# Icon

Renders a Phosphor icon with size and color set through class names.

Icon is an open-source component from suiss/ui for React Native, SwiftUI and Jetpack Compose. The @esuiss/ui CLI copies its source into your project, so you own the code.

Source: https://ui.suiss.app/docs/components/icon

**React Native, SwiftUI, Jetpack Compose**

![Preview](https://ui.suiss.app/previews/components-icon--default.light.webp)

```tsx
import { StarIcon } from "phosphor-react-native"
import { Icon } from "@/components/ui/icon"

export function IconExample() {
  return (
    <Icon as={StarIcon} />
  )
}
```

```swift
import EsuissUI
import PhosphorSwift
import SwiftUI

struct IconExample: View {
  var body: some View {
    IconGlyph(image: Ph.star.regular)
  }
}

struct IconGlyph: View {
  let image: Image
  var size: CGFloat = 20
  var color: Color = EsuissColor.foreground

  var body: some View {
    image
      .resizable()
      .scaledToFit()
      .frame(width: size, height: size)
      .foregroundStyle(color)
  }
}
```

```kotlin
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Row
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.unit.dp
import com.adamglin.PhosphorIcons
import com.adamglin.phosphoricons.Regular
import com.adamglin.phosphoricons.regular.Star
import com.esuiss.ui.components.Icon

@Composable
fun IconRow(content: @Composable () -> Unit) {
    Row(horizontalArrangement = Arrangement.spacedBy(16.dp), verticalAlignment = Alignment.CenterVertically) {
        content()
    }
}

@Composable
fun IconDefault() {
    IconRow {
        Icon(PhosphorIcons.Regular.Star, contentDescription = null)
    }
}
```

**React Native, Jetpack Compose**

`Icon` takes a Phosphor icon component in `as` and maps `size-*` classes to its size and `text-*` classes to its color. It defaults to `size-5 text-foreground`.

Icons inherit text and icon classes from their parent, so an `Icon` inside a `Button`, `Badge` or menu item picks up the right size and color without extra classes. Components provide these through `IconClassContext`.

**SwiftUI**

SwiftUI has no `Icon` view to install. Use the images from the Phosphor Swift package directly, and set their size and color with SwiftUI modifiers, as the examples on this page do.

## Installation

**React Native**

```bash title="Terminal"
npx shadcn@latest add @esuiss-rn/icon
```

See [Installation](https://ui.suiss.app/docs/installation) for the `components.json` setup. Or use the suiss/ui CLI:

```bash title="Terminal"
npx @esuiss/ui@latest add icon
```

**Jetpack Compose**

```bash title="Terminal"
npx @esuiss/ui@latest add icon --platform kotlin
```

**SwiftUI**

Add the Phosphor Swift package to your app:

```swift title="Package.swift"
.package(url: "https://github.com/phosphor-icons/swift", from: "2.1.0")
```

**React Native, Jetpack Compose**

The CLI also adds what it builds on: [Text](https://ui.suiss.app/docs/components/text).

**React Native, Jetpack Compose**

### Manual

**React Native**

**Uniwind**

1. Install the dependencies:

```bash title="Terminal"
npx expo install cn phosphor-react-native react-native-svg uniwind
```

2. Add the pieces it builds on: [Text](https://ui.suiss.app/docs/components/text).

3. Copy the source into `components/ui/icon.tsx`:

```tsx title="uniwind/components/ui/icon.tsx"
import { cn } from "cn"
import type {
  Icon as PhosphorIcon,
  IconProps as PhosphorIconProps,
} from "phosphor-react-native"
import * as React from "react"
import { withUniwind } from "uniwind"
import { TextClassContext } from "@/components/ui/text"

type IconProps = PhosphorIconProps & {
  as: PhosphorIcon
  className?: string
}

const IconClassContext = React.createContext<string | undefined>(undefined)

function IconImpl({ as: IconComponent, ...props }: IconProps) {
  return <IconComponent {...props} />
}

const StyledIcon = withUniwind(IconImpl, {
  size: {
    fromClassName: "className",
    styleProperty: "width",
  },
  color: {
    fromClassName: "className",
    styleProperty: "color",
  },
})

function Icon({ as: IconComponent, className, ...props }: IconProps) {
  const textClass = React.useContext(TextClassContext)
  const iconClass = React.useContext(IconClassContext)
  return (
    <StyledIcon
      as={IconComponent}
      className={cn("size-5 text-foreground", textClass, iconClass, className)}
      {...props}
    />
  )
}

export type { IconProps }
export { Icon, IconClassContext }
```

4. Update the import paths to match your project.

**NativeWind**

1. Install the dependencies:

```bash title="Terminal"
npx expo install cn nativewind phosphor-react-native react-native-svg
```

2. Add the pieces it builds on: [Text](https://ui.suiss.app/docs/components/text).

3. Copy the source into `components/ui/icon.tsx`:

```tsx title="nativewind/components/ui/icon.tsx"
import { cn } from "cn"
import { cssInterop } from "nativewind"
import type {
  Icon as PhosphorIcon,
  IconProps as PhosphorIconProps,
} from "phosphor-react-native"
import * as React from "react"
import { TextClassContext } from "@/components/ui/text"

type IconProps = PhosphorIconProps & {
  as: PhosphorIcon
  className?: string
}

const IconClassContext = React.createContext<string | undefined>(undefined)

function IconImpl({ as: IconComponent, ...props }: IconProps) {
  return <IconComponent {...props} />
}

const StyledIcon = cssInterop(IconImpl, {
  className: {
    target: "style",
    nativeStyleToProp: {
      color: true,
      width: "size",
    },
  },
})

function Icon({ as: IconComponent, className, ...props }: IconProps) {
  const textClass = React.useContext(TextClassContext)
  const iconClass = React.useContext(IconClassContext)
  return (
    <StyledIcon
      as={IconComponent}
      className={cn("size-5 text-foreground", textClass, iconClass, className)}
      {...props}
    />
  )
}

export type { IconProps }
export { Icon, IconClassContext }
```

4. Update the import paths to match your project.

**StyleSheet**

1. Install the dependencies:

```bash title="Terminal"
npx expo install phosphor-react-native react-native-svg
```

2. Add the pieces it builds on: [Text](https://ui.suiss.app/docs/components/text).

3. Copy the source into `components/ui/icon.tsx`:

```tsx title="stylesheet/components/ui/icon.tsx"
import type {
  Icon as PhosphorIcon,
  IconProps as PhosphorIconProps,
} from "phosphor-react-native"
import * as React from "react"
import { TextStyleContext } from "@/components/ui/text"
import { useTheme } from "@/components/ui/theme"

type IconProps = PhosphorIconProps & {
  as: PhosphorIcon
}

const IconSizeContext = React.createContext<number | undefined>(undefined)

function Icon({ as: IconComponent, size, color, ...props }: IconProps) {
  const { colors } = useTheme()
  const inherited = React.useContext(TextStyleContext)
  const inheritedSize = React.useContext(IconSizeContext)
  const inheritedColor =
    typeof inherited?.color === "string" ? inherited.color : undefined
  return (
    <IconComponent
      size={size ?? inheritedSize ?? 20}
      color={color ?? inheritedColor ?? colors.foreground}
      {...props}
    />
  )
}

export type { IconProps }
export { Icon, IconSizeContext }
```

4. Update the import paths to match your project.

**Jetpack Compose**

1. Set `minSdk` to 24 or higher and add the dependencies:

```kotlin title="build.gradle.kts"
implementation(platform("androidx.compose:compose-bom:2026.09.00"))
implementation("androidx.compose.foundation:foundation")
implementation("androidx.compose.ui:ui")
```

2. Copy the theme files every view uses:

- `kotlin/theme/Theme.kt`: [source](https://github.com/e-suiss/ui/blob/main/packages/mobile/kotlin/ui/src/main/kotlin/com/esuiss/ui/theme/Theme.kt)

- `kotlin/theme/Tokens.kt`: [source](https://github.com/e-suiss/ui/blob/main/packages/mobile/kotlin/ui/src/main/kotlin/com/esuiss/ui/theme/Tokens.kt)

3. Copy the source and every file it uses:

```kt title="kotlin/components/Icon.kt"
package com.esuiss.ui.components

import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.size
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.graphics.takeOrElse
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.unit.Dp
import com.esuiss.ui.theme.EsuissTheme

@Composable
fun Icon(
    imageVector: ImageVector,
    contentDescription: String?,
    modifier: Modifier = Modifier,
    size: Dp = LocalIconSize.current,
    tint: Color = LocalContentColor.current.takeOrElse { EsuissTheme.colors.foreground },
) {
    Image(
        imageVector = imageVector,
        contentDescription = contentDescription,
        modifier = modifier.size(size),
        colorFilter = ColorFilter.tint(tint),
    )
}
```

- `kotlin/components/Content.kt`: [source](https://github.com/e-suiss/ui/blob/main/packages/mobile/kotlin/ui/src/main/kotlin/com/esuiss/ui/components/Content.kt)

4. Update the `com.esuiss.ui` package names to match your project.

**React Native**

## Usage

```tsx
import { Icon } from "@/components/ui/icon"
```

```tsx
<Icon as={StarIcon} />
```

Pass the icon component, not an element: `<Icon as={HeartIcon} />`. Override the size and color with `className`, such as `size-6 text-destructive`. Other Phosphor props, such as `weight` and `mirrored`, are passed through.

Wrap a subtree in `IconClassContext.Provider` to set default classes for the icons inside your own components.

## Examples

### Sizes

Icons at several `size-*` classes.

![Preview](https://ui.suiss.app/previews/components-icon--sizes.light.webp)

```tsx
import { BellIcon } from "phosphor-react-native"
import { View } from "react-native"
import { Icon } from "@/components/ui/icon"

export function IconSizesExample() {
  return (
    <View className="flex-row items-center gap-4">
      <Icon as={BellIcon} className="size-4" />
      <Icon as={BellIcon} />
      <Icon as={BellIcon} className="size-6" />
      <Icon as={BellIcon} className="size-8" />
    </View>
  )
}
```

```swift
import EsuissUI
import PhosphorSwift
import SwiftUI

struct IconSizesExample: View {
  var body: some View {
    HStack(spacing: 16) {
      IconGlyph(image: Ph.bell.regular, size: 16)
      IconGlyph(image: Ph.bell.regular)
      IconGlyph(image: Ph.bell.regular, size: 24)
      IconGlyph(image: Ph.bell.regular, size: 32)
    }
  }
}

struct IconGlyph: View {
  let image: Image
  var size: CGFloat = 20
  var color: Color = EsuissColor.foreground

  var body: some View {
    image
      .resizable()
      .scaledToFit()
      .frame(width: size, height: size)
      .foregroundStyle(color)
  }
}
```

```kotlin
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Row
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.unit.dp
import com.adamglin.PhosphorIcons
import com.adamglin.phosphoricons.Regular
import com.adamglin.phosphoricons.regular.Bell
import com.esuiss.ui.components.Icon

@Composable
fun IconRow(content: @Composable () -> Unit) {
    Row(horizontalArrangement = Arrangement.spacedBy(16.dp), verticalAlignment = Alignment.CenterVertically) {
        content()
    }
}

@Composable
fun IconSizes() {
    IconRow {
        Icon(PhosphorIcons.Regular.Bell, contentDescription = null, size = 16.dp)
        Icon(PhosphorIcons.Regular.Bell, contentDescription = null)
        Icon(PhosphorIcons.Regular.Bell, contentDescription = null, size = 24.dp)
        Icon(PhosphorIcons.Regular.Bell, contentDescription = null, size = 32.dp)
    }
}
```

### Colors

Icons in different color tokens.

![Preview](https://ui.suiss.app/previews/components-icon--colors.light.webp)

```tsx
import { GearIcon, HeartIcon, InfoIcon, WarningIcon } from "phosphor-react-native"
import { View } from "react-native"
import { Icon } from "@/components/ui/icon"

export function IconColorsExample() {
  return (
    <View className="flex-row items-center gap-4">
      <Icon as={InfoIcon} />
      <Icon as={GearIcon} className="text-muted-foreground" />
      <Icon as={HeartIcon} className="text-primary" weight="fill" />
      <Icon as={WarningIcon} className="text-destructive" />
    </View>
  )
}
```

```swift
import EsuissUI
import PhosphorSwift
import SwiftUI

struct IconColorsExample: View {
  var body: some View {
    HStack(spacing: 16) {
      IconGlyph(image: Ph.info.regular)
      IconGlyph(image: Ph.gear.regular, color: EsuissColor.mutedForeground)
      IconGlyph(image: Ph.heart.fill, color: EsuissColor.primary)
      IconGlyph(image: Ph.warning.regular, color: EsuissColor.destructive)
    }
  }
}

struct IconGlyph: View {
  let image: Image
  var size: CGFloat = 20
  var color: Color = EsuissColor.foreground

  var body: some View {
    image
      .resizable()
      .scaledToFit()
      .frame(width: size, height: size)
      .foregroundStyle(color)
  }
}
```

```kotlin
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Row
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.unit.dp
import com.adamglin.PhosphorIcons
import com.adamglin.phosphoricons.Fill
import com.adamglin.phosphoricons.Regular
import com.adamglin.phosphoricons.regular.Gear
import com.adamglin.phosphoricons.regular.Info
import com.adamglin.phosphoricons.regular.Warning
import com.esuiss.ui.components.Icon
import com.esuiss.ui.theme.EsuissTheme

@Composable
fun IconRow(content: @Composable () -> Unit) {
    Row(horizontalArrangement = Arrangement.spacedBy(16.dp), verticalAlignment = Alignment.CenterVertically) {
        content()
    }
}

@Composable
fun IconColors() {
    val colors = EsuissTheme.colors
    IconRow {
        Icon(PhosphorIcons.Regular.Info, contentDescription = null)
        Icon(PhosphorIcons.Regular.Gear, contentDescription = null, tint = colors.mutedForeground)
        Icon(PhosphorIcons.Fill.FillHeart, contentDescription = null, tint = colors.primary)
        Icon(PhosphorIcons.Regular.Warning, contentDescription = null, tint = colors.destructive)
    }
}
```

### Weights

The available Phosphor weights.

![Preview](https://ui.suiss.app/previews/components-icon--weights.light.webp)

```tsx
import { StarIcon } from "phosphor-react-native"
import { View } from "react-native"
import { Icon } from "@/components/ui/icon"

export function IconWeightsExample() {
  return (
    <View className="flex-row items-center gap-4">
      <Icon as={StarIcon} className="size-6" weight="thin" />
      <Icon as={StarIcon} className="size-6" weight="light" />
      <Icon as={StarIcon} className="size-6" weight="regular" />
      <Icon as={StarIcon} className="size-6" weight="bold" />
      <Icon as={StarIcon} className="size-6" weight="fill" />
      <Icon as={StarIcon} className="size-6" weight="duotone" />
    </View>
  )
}
```

```swift
import EsuissUI
import PhosphorSwift
import SwiftUI

struct IconWeightsExample: View {
  var body: some View {
    HStack(spacing: 16) {
      IconGlyph(image: Ph.star.thin, size: 24)
      IconGlyph(image: Ph.star.light, size: 24)
      IconGlyph(image: Ph.star.regular, size: 24)
      IconGlyph(image: Ph.star.bold, size: 24)
      IconGlyph(image: Ph.star.fill, size: 24)
      IconGlyph(image: Ph.star.duotone, size: 24)
    }
  }
}

struct IconGlyph: View {
  let image: Image
  var size: CGFloat = 20
  var color: Color = EsuissColor.foreground

  var body: some View {
    image
      .resizable()
      .scaledToFit()
      .frame(width: size, height: size)
      .foregroundStyle(color)
  }
}
```

```kotlin
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Row
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.unit.dp
import com.adamglin.PhosphorIcons
import com.adamglin.phosphoricons.Bold
import com.adamglin.phosphoricons.Duotone
import com.adamglin.phosphoricons.Fill
import com.adamglin.phosphoricons.Light
import com.adamglin.phosphoricons.Regular
import com.adamglin.phosphoricons.Thin
import com.adamglin.phosphoricons.regular.Star
import com.esuiss.ui.components.Icon

@Composable
fun IconRow(content: @Composable () -> Unit) {
    Row(horizontalArrangement = Arrangement.spacedBy(16.dp), verticalAlignment = Alignment.CenterVertically) {
        content()
    }
}

@Composable
fun IconWeights() {
    IconRow {
        Icon(PhosphorIcons.Thin.ThinStar, contentDescription = null, size = 24.dp)
        Icon(PhosphorIcons.Light.LightStar, contentDescription = null, size = 24.dp)
        Icon(PhosphorIcons.Regular.Star, contentDescription = null, size = 24.dp)
        Icon(PhosphorIcons.Bold.BoldStar, contentDescription = null, size = 24.dp)
        Icon(PhosphorIcons.Fill.FillStar, contentDescription = null, size = 24.dp)
        Icon(PhosphorIcons.Duotone.DuotoneStar, contentDescription = null, size = 24.dp)
    }
}
```

**React Native, Jetpack Compose**

## API Reference

### Icon

Styled Phosphor icon.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `as` | `PhosphorIcon` | Required | The Phosphor icon component to render. |

Also accepts the props of `IconProps`.

## Accessibility

- Icons have no label of their own. When an icon is the only content of a pressable, give the pressable an `accessibilityLabel`.

## Notes

- The default color is the `text-foreground` token, so icons follow light and dark mode with the system appearance.

