alouette
Version:
A modern, customizable design system built on top of NativeWind v5 with configurable defaults
197 lines (145 loc) • 5.7 kB
Markdown
---
name: alouette-icons
description: >
Use Phosphor icons with alouette. Each icon is imported from its own module
(alouette-icons/phosphor-icons/IconName) so only what is used is bundled, and
is then either rendered through the Icon component or handed as an element to
a component that takes one (Button, IconButton, Message). Its color comes from
a text-* token class rather than from the SVG, so an icon follows the theme
and the accent around it. Load when adding icons to alouette UI.
type: composition
library: alouette
requires:
- alouette-theming
sources:
- "christophehurpeau/alouette:packages/alouette/src/ui/primitives/Icon.tsx"
- "christophehurpeau/alouette:packages/alouette/src/ui/primitives/InteractiveIcon.tsx"
- "christophehurpeau/alouette:packages/alouette/src/ui/actions/Button.stories.tsx"
- "christophehurpeau/alouette:CLAUDE.md"
---
This skill builds on alouette-theming. Read it first for color tokens.
# alouette + alouette-icons
Icons come from the `alouette-icons` package (auto-generated Phosphor icons).
Import each icon individually, then render it through alouette's `Icon` component
or pass the element to a component's `icon` prop. `Icon` resolves a `text-*`
className to a concrete color token and applies it as the SVG color.
## Setup
```tsx
import { Icon } from "alouette";
import { HeartRegularIcon } from "alouette-icons/phosphor-icons/HeartRegularIcon";
<Icon icon={<HeartRegularIcon />} size={24} className="text-accent" />;
```
## Core Patterns
### Icons inside alouette components
`Button`, `IconButton` and `Message` take an `icon` element directly — they size
and tint it for you.
```tsx
import { Button, IconButton } from "alouette";
import { ArrowLeftRegularIcon } from "alouette-icons/phosphor-icons/ArrowLeftRegularIcon";
import { XRegularIcon } from "alouette-icons/phosphor-icons/XRegularIcon";
<Button text="Back" icon={<ArrowLeftRegularIcon />} />
<IconButton icon={<XRegularIcon />} aria-label="Close" onPress={close} />
```
### Duotone on interaction
`activeIcon` is the interaction twin of `icon`: it replaces it while the
pressable is hovered, focused or pressed, and stays for as long as the item is
selected (`NavBarItem` on the current page, the selected `Tab`, the checked
`RadioButton`). The duotone weight of the same glyph is the intended pairing.
Available on `Button`, `IconButton`, `MenuItem`, `NavBarItem`, `Tab` and
`RadioButton`; a disabled control never swaps.
```tsx
import { HouseDuotoneIcon } from "alouette-icons/phosphor-icons/HouseDuotoneIcon";
import { HouseRegularIcon } from "alouette-icons/phosphor-icons/HouseRegularIcon";
<NavBarItem
href="/home"
label="Home"
icon={<HouseRegularIcon />}
activeIcon={<HouseDuotoneIcon />}
/>;
```
`NavBarItem`, `Tab` and `RadioButton` also take `activeAccent`, tinting the
swapped-in glyph with an accent of its own so it changes color as well as weight.
It reaches the icon through `text-accent`, the only token an accent theme
redeclares for a foreground glyph — an accent scope around an icon that keeps its
base tint (`text-sharp`, `text-muted`) does nothing.
```tsx
<NavBarItem
href="/archive"
label="Archive"
icon={<TrashRegularIcon />}
activeIcon={<TrashDuotoneIcon />}
activeAccent="danger"
/>
```
`InteractiveIcon` is the exported primitive behind both, for a pressable you
build yourself out of `PressableBox` — it cross-fades the two layers off the
pressable's `group`, so it only works inside one.
### Tinting
Tint follows the current accent when you use `text-accent`:
```tsx
<Icon icon={<HeartRegularIcon />} className="text-muted" />
<Box accent="danger">
<Icon icon={<HeartRegularIcon />} className="text-accent" />
</Box>
```
## Common Mistakes
### MEDIUM Barrel-importing the icon set
Wrong:
```tsx
import { ArrowLeftRegularIcon } from "alouette-icons";
```
Correct:
```tsx
import { ArrowLeftRegularIcon } from "alouette-icons/phosphor-icons/ArrowLeftRegularIcon";
```
Icons are individually importable modules; a barrel import pulls in the entire
generated set and bloats the bundle.
Source: packages/alouette/src/ui/actions/Button.stories.tsx; CLAUDE.md (Icons)
### MEDIUM Tinting an icon with a color prop
Wrong:
```tsx
<Icon icon={<InfoRegularIcon />} color="#888" />
```
Correct:
```tsx
<Icon icon={<InfoRegularIcon />} className="text-muted" />
```
`Icon` resolves a `text-*` className to a `--color-*` token and passes it as the
SVG color; a raw `color` bypasses theming and breaks mode/accent adaptation.
Source: packages/alouette/src/ui/primitives/Icon.tsx
### MEDIUM Sizing an icon with width/height classes
Wrong:
```tsx
<Icon icon={<InfoRegularIcon />} className="w-6 h-6" />
```
Correct:
```tsx
<Icon icon={<InfoRegularIcon />} size={24} />
```
`Icon` takes a numeric `size` prop (px) applied as the SVG width/height; `w-*` /
`h-*` classes are not how alouette sizes icons.
Source: packages/alouette/src/ui/primitives/Icon.tsx
### MEDIUM Swapping the icon weight with local hover state
Wrong:
```tsx
const [hovered, setHovered] = useState(false);
<Button
icon={hovered ? <HouseDuotoneIcon /> : <HouseRegularIcon />}
onHoverIn={() => setHovered(true)}
onHoverOut={() => setHovered(false)}
text="Home"
/>;
```
Correct:
```tsx
<Button
icon={<HouseRegularIcon />}
activeIcon={<HouseDuotoneIcon />}
text="Home"
/>
```
`activeIcon` cross-fades the two glyphs in CSS off the pressable's `group`, so it
covers focus and press as well as hover and never re-renders the row. A JS hover
state covers hover only, re-renders on every pointer move in and out, and has no
keyboard equivalent.
Source: packages/alouette/src/ui/primitives/InteractiveIcon.tsx