# Icon

> Every glyph: a Lucide icon at a Token size in a Colour Role, decorative unless labelled.

Component · core. Registry item `icon`, shown in the vega Style. JSON: https://nativecn.dev/r/styles/vega/icon.json

## Installation

```sh
npx nativecn-cli@latest add icon
```

Also installs: [theme](https://nativecn.dev/docs/theme/theme).

Packages: `lucide-react-native@^1.51.0`, `react-native-svg`.

## Usage

### Icon demo

Icons in each size, a Colour Role, a stroke width and a labelled icon.

```tsx
import { Bell, Heart, Settings } from "lucide-react-native";
import { View } from "react-native";

import { Icon } from "@/components/icon";
import { createStyles } from "@/theme";

/** Each size, a Colour Role, a stroke width and a labelled (meaningful) icon. */
export default function IconDemo() {
  const styles = useStyles();
  return (
    <View style={styles.row}>
      <Icon icon={Settings} size="sm" />
      <Icon icon={Settings} />
      <Icon icon={Settings} size="lg" />
      <Icon icon={Heart} color="destructive" strokeWidth={2.5} />
      {/* Decorative unless it carries meaning on its own: then name it. */}
      <Icon icon={Bell} color="primary" aria-label="New notifications" />
    </View>
  );
}

const useStyles = createStyles((t) => ({
  row: { flexDirection: "row", alignItems: "center", gap: t.spacing[4] },
}));
```

## Props

| Prop | Type and description |
|---|---|
| `icon` | LucideIcon (required), e.g. import { Camera } from "lucide-react-native" |
| `size` | "sm" \| "md" (default) \| "lg": 16 · 20 · 24 from the iconSize Tokens, scaled |
| `color` | a Colour Role (default foreground) |
| `strokeWidth` | number (default 2) |
| `aria-label` | string: makes the icon meaningful (role img); without it the icon is decorative and hidden |
| `style` | ViewStyle, merged last |

## Variants

- `size`: `sm`, `md`, `lg`

## Accessibility

- Decorative by default: hidden from screen readers.
- With aria-label it has role img and is read with that name.
- Inside a labelled control (a Button, a ListItem) leave it decorative: the control carries the name.

## Notes

Every glyph goes through Icon, so iOS and Android draw the same Lucide glyphs; never use @expo/vector-icons or SF Symbols directly. lucide-react-native is a recorded dependency exception and react-native-svg is SDK-pinned.

## Source

### {components}/icon.tsx

```tsx
import type { LucideIcon } from "lucide-react-native";
import type { Ref } from "react";
import { View, type ViewProps } from "react-native";

import { createStyles, useTheme, type ColorRole } from "@/theme";

/** Icon sizes, from the `iconSize` Tokens (scaled). */
export type IconSize = "sm" | "md" | "lg";

export type IconProps = Omit<ViewProps, "children"> & {
  /** A Lucide icon component, e.g. `import { Camera } from "lucide-react-native"`. */
  icon: LucideIcon;
  /** sm 16 · md 20 · lg 24, before Scale (default `md`). */
  size?: IconSize;
  /** A Colour Role (default `foreground`). */
  color?: ColorRole;
  /** Stroke width of the glyph (default `2`). */
  strokeWidth?: number;
  /** Makes the icon meaningful to screen readers. Without it the icon is decorative and hidden. */
  "aria-label"?: string;
  ref?: Ref<View>;
};

const useStyles = createStyles((t) => ({
  sm: { width: t.iconSize.sm, height: t.iconSize.sm },
  md: { width: t.iconSize.md, height: t.iconSize.md },
  lg: { width: t.iconSize.lg, height: t.iconSize.lg },
}));

/**
 * Every glyph in the app: draws a Lucide icon at a Token size in a Colour Role. Decorative by
 * default (hidden from screen readers); pass `aria-label` when the icon carries meaning on its
 * own. `style` is merged last.
 */
export function Icon({
  icon: Glyph,
  size = "md",
  color = "foreground",
  strokeWidth = 2,
  "aria-label": label,
  style,
  ...props
}: IconProps) {
  const { colors, iconSize } = useTheme();
  const styles = useStyles();
  const decorative = !label;

  return (
    <View
      role={decorative ? undefined : "img"}
      aria-label={label}
      aria-hidden={decorative}
      accessible={!decorative}
      importantForAccessibility={decorative ? "no-hide-descendants" : "yes"}
      style={[styles[size], style]}
      {...props}
    >
      <Glyph size={iconSize[size]} color={colors[color]} strokeWidth={strokeWidth} />
    </View>
  );
}
```
