suiss/uim · React Native Component
Icon
Renders a Phosphor icon with size and color set through class names.
Icon is an open-source mobile component from suiss/uim for React Native and Expo, with SwiftUI and Jetpack Compose versions built from the same design tokens. The @esuiss/uim CLI copies its source into your project, so you own the code.
import { StarIcon } from "phosphor-react-native"
import { Icon } from "@/components/ui/icon"
export function IconExample() {
return (
<Icon as={StarIcon} />
)
}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 = .label
var body: some View {
image
.resizable()
.scaledToFit()
.frame(width: size, height: size)
.foregroundStyle(color)
}
}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)
}
}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-label.
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.
Installation
$ pnpm dlx @esuiss/uim@latest add icon$ pnpm dlx @esuiss/uim@latest add icon --platform swift$ pnpm dlx @esuiss/uim@latest add icon --platform kotlinThe CLI also adds what it builds on: Text.
Manual
- Install the dependencies:
npx expo install cn phosphor-react-native react-native-svg uniwind-
Add the pieces it builds on: Text.
-
Copy the source into
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)- Update the import paths to match your project.
- Install the dependencies:
npx expo install cn nativewind phosphor-react-native react-native-svg-
Add the pieces it builds on: Text.
-
Copy the source into
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)- Update the import paths to match your project.
- Install the dependencies:
npx expo install phosphor-react-native react-native-svg-
Add the pieces it builds on: Text.
-
Copy the source into
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()- Update the import paths to match your project.
- Set
minSdkto 24 or higher and add the dependencies:
implementation(platform("androidx.compose:compose-bom:2026.09.00"))
implementation("androidx.compose.foundation:foundation")
implementation("androidx.compose.ui:ui")- Copy the theme files every view uses:
package com.esuiss.ui.theme
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.Immutable
import androidx.compose.runtime.staticCompositionLocalOf
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.dropShadow
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.graphics.shadow.Shadow as DropShadow
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.style.LineHeightStyle
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.DpOffsetpackage com.esuiss.ui.theme
import androidx.compose.runtime.Immutable
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
@Immutable
data class Colors(
val surface: Color,
val surfaceSecondary: Color,
val surfaceTertiary: Color,
val surfaceRaised: Color,
val label: Color,
val labelSecondary: Color,
val labelTertiary: Color,
val labelQuaternary: Color,- Copy the source and every file it uses:
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,package com.esuiss.ui.components
import androidx.compose.runtime.compositionLocalOf
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
val LocalContentStyle = compositionLocalOf { TextStyle.Default }
val LocalContentColor = compositionLocalOf { Color.Unspecified }
val LocalIconSize = compositionLocalOf { 20.dp }- Update the
com.esuiss.uipackage names to match your project.
Usage
import { Icon } from "@/components/ui/icon"<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-danger. 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.
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>
)
}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 = .label
var body: some View {
image
.resizable()
.scaledToFit()
.frame(width: size, height: size)
.foregroundStyle(color)
}
}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.
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-label-secondary" />
<Icon as={HeartIcon} className="text-accent" weight="fill" />
<Icon as={WarningIcon} className="text-danger" />
</View>
)
}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: .labelSecondary)
IconGlyph(image: Ph.heart.fill, color: .accent)
IconGlyph(image: Ph.warning.regular, color: .danger)
}
}
}
struct IconGlyph: View {
let image: Image
var size: CGFloat = 20
var color: Color = .label
var body: some View {
image
.resizable()
.scaledToFit()
.frame(width: size, height: size)
.foregroundStyle(color)
}
}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.labelSecondary)
Icon(PhosphorIcons.Fill.FillHeart, contentDescription = null, tint = colors.accent)
Icon(PhosphorIcons.Regular.Warning, contentDescription = null, tint = colors.danger)
}
}Weights
The available Phosphor weights.
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>
)
}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 = .label
var body: some View {
image
.resizable()
.scaledToFit()
.frame(width: size, height: size)
.foregroundStyle(color)
}
}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)
}
}API Reference
Icon
Styled Phosphor icon.
IconPropsAccessibility
- 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-labeltoken, so icons follow light and dark mode with the system appearance.