Icons
Farn does not ship an icon set. Icons are a large, opinionated surface — hundreds of glyphs, multiple weights, a release cadence of their own — and bundling one would tie Farn to choices that belong to your project, not the token layer. Instead, Farn gives you the sizingand colour tokens to make any third-party set feel native.
Recommended library
Lucideis the recommended default — 1,700+ icons, ISC licence, a clean stroke character that sits naturally with Farn's restrained aesthetic, and every icon uses currentColor out of the box. It is the only library in the space with an official lucide-astropackage, and the lucide-static package ships individual SVG files for any framework or no framework at all.
Install
# Astro — official component package
npm install lucide-astro
# Vanilla / any bundler — individual SVG files
npm install lucide-staticInline SVG (vanilla)
Browse lucide.dev/icons, click an icon, and copy the <svg> markup. Paste it directly into your HTML. The default stroke="currentColor" means the icon inherits the surrounding text colour without any extra CSS.
<!-- Paste any icon from lucide.dev — stroke="currentColor" is the default -->
<svg xmlns="http://www.w3.org/2000/svg"
width="24" height="24" viewBox="0 0 24 24"
fill="none" stroke="currentColor" stroke-width="2"
stroke-linecap="round" stroke-linejoin="round"
aria-hidden="true">
<path d="M5 12h14"/>
<path d="m12 5 7 7-7 7"/>
</svg>Astro components
---
import { ArrowRight, Check, Info } from 'lucide-astro';
---
<!-- Decorative icon next to a label -->
<button>
<ArrowRight size={20} aria-hidden="true" />
Continue
</button>
<!-- Icon-only control — label goes on the button, not the icon -->
<button aria-label="Next page">
<ArrowRight size={20} aria-hidden="true" />
</button>ti-brand-github, ti-brand-figma, etc.) — Lucide intentionally excludes third-party brand marks. Both use currentColor and work with the same sizing and colour patterns below.Size with spacing tokens
Drive icon dimensions from the spacing scale so they track the rest of the system. As a rule of thumb: inline icons match the surrounding cap height; standalone affordances sit one step up.
--space-md, 24px)--space-lg, 36px)| Context | Token | Value |
|---|---|---|
| Inline with text | 1em | tracks font-size |
| Button / input affordance | --space-md | 24px |
| Standalone / feature | --space-lg | 36px |
.icon {
width: var(--space-md); /* 24px — button and input icons */
height: var(--space-md);
}
.icon-inline {
width: 1em; /* tracks surrounding text size */
height: 1em;
vertical-align: -0.125em; /* optical baseline nudge */
}
.icon-feature {
width: var(--space-lg); /* 36px — standalone callout or feature icon */
height: var(--space-lg);
}Colour with semantic tokens
Control icon colour through the parent's color property — SVG icons inherit it via currentColor regardless of whether they use stroke or fill. Lucide usesstroke="currentColor" by default; Phosphor Fill and Tabler filled variants usefill="currentColor". Either way, Farn's semantic tokens resolve per theme and surface, so an icon coloured with var(--color-text) stays legible everywhere — no dark-mode overrides needed.
--color-text--color-accent--color-text-secondary--color-error--color-success/* Drive icon colour through the parent — icons inherit currentColor */
.icon-accent { color: var(--color-accent); }
.icon-muted { color: var(--color-text-secondary); }
.icon-error { color: var(--color-error); }
.icon-success { color: var(--color-success); }Toggle the page theme to verify — the demo icons above adapt to light, dark, and everydata-surface context automatically.
Accessibility
- Decorative icon (next to a label) — add
aria-hidden="true". The visible text already conveys the meaning; the icon is presentational. - Icon-only control (no visible label) — put the accessible name on the interactive element (
aria-labelon the<button>), and addaria-hidden="true"to the icon itself. - Standalone icon conveying meaning — use
role="img"and a<title>inside the SVG so screen readers announce it. - Colour alone — never use icon colour as the sole state signal. Pair a semantic colour with a distinct shape or a visible label.
<!-- Decorative — icon sits next to a text label -->
<button>
<svg aria-hidden="true">...</svg>
Save changes
</button>
<!-- Icon-only control — accessible name lives on the button -->
<button aria-label="Close dialog">
<svg aria-hidden="true">...</svg>
</button>
<!-- Standalone meaningful icon — title is read aloud -->
<svg role="img" aria-labelledby="icon-title">
<title id="icon-title">Warning</title>
...
</svg>