Icon
Every glyph: a Lucide icon at a Token size in a Colour Role, decorative unless labelled.
Componentcore
Style
Light
Dark
Your app is ready.
Inter body text, Inter headings.
smmdlgxl
you@example.com
Continue
a0: Vega, neutral, neutral accent, default radius, Inter.Change PresetInstallation
$
npx nativecn-cli@latest add iconAlso installs: 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.
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] },
}));Icon demo
Icons in each size, a Colour Role, a stroke width and a labelled icon.
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
The Vega Style's code, exactly as add copies it into your app.
{components}/icon.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>
);
}{components}/icon.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>
);
}
