Skip to content

Commit eeafa7d

Browse files
committed
Add icons guide.
1 parent 4363f2d commit eeafa7d

3 files changed

Lines changed: 289 additions & 1 deletion

File tree

‎frontend/purestack.studio/guides/_nav.json‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
"typography.mdx",
1010
"utility-css-classes.mdx",
1111
"semantic-tones.mdx",
12+
"icons.mdx",
1213
"themes.mdx"
1314
]
1415
}
Lines changed: 282 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,282 @@
1+
---
2+
title: Icons
3+
description: Find icon names, place icons in pages and components, set them in navigation and site config, and register them in browser apps.
4+
template: doc
5+
layout:
6+
showNav: true
7+
showToc: true
8+
showFooter: false
9+
nav:
10+
order: 39
11+
icon: tabler:icons
12+
---
13+
14+
# Icons
15+
16+
PureStack includes about 11,000 SVG icons from four open-source sets. You refer to each icon by a string name, and the same name works in MDX, component props, navigation, and the site configuration. The build inlines the icon's SVG markup in the page, so icons need no icon font, sprite sheet, or extra request.
17+
18+
<Flex wrap="true" align="center" class="fs-h3">
19+
<Icon name="tabler:rocket" aria-hidden="true" />
20+
<Icon name="lucide:search" aria-hidden="true" />
21+
<Icon name="iconoir:code" aria-hidden="true" />
22+
<Icon name="phosphor:acorn" aria-hidden="true" />
23+
<Icon name="tabler:heart-filled" aria-hidden="true" class="tone--danger tone-text" />
24+
<Icon name="tabler:shield-check" aria-hidden="true" class="tone--success tone-text" />
25+
<Icon name="tabler:sparkles" aria-hidden="true" class="tone--accent tone-text" />
26+
</Flex>
27+
28+
## Name an icon
29+
30+
An icon name is the collection, a colon, and the icon's name in that collection:
31+
32+
```text
33+
tabler:rocket
34+
lucide:search
35+
iconoir:code
36+
phosphor:acorn
37+
```
38+
39+
| Collection | Prefix | Icons | Variants |
40+
| --- | --- | --- | --- |
41+
| [Tabler](https://tabler.io/icons) | `tabler:` | 6,092 | Outline. Add `-filled` for the solid version, such as `tabler:heart-filled`. |
42+
| [Lucide](https://lucide.dev/icons/) | `lucide:` | 1,702 | Outline only. |
43+
| [Iconoir](https://iconoir.com/) | `iconoir:` | 1,671 | Outline. Add `-solid` where a solid version exists, such as `iconoir:check-circle-solid`. |
44+
| [Phosphor](https://phosphoricons.com/) | `phosphor:` | 1,512 | Regular weight only. |
45+
46+
Each set has its own stroke weight and corner style. Pick one set for most of the interface so that icons look consistent. Use a second set only when the first one does not have the symbol you need. This site uses Tabler for navigation and Lucide inside components.
47+
48+
## Find a valid name
49+
50+
Icon names must match exactly. Browse a collection's website to find a symbol, then confirm the name in the list that PureStack ships. Use the generated file for that collection:
51+
52+
```text
53+
packages/ts-svg-icons/src/tabler.generated.ts
54+
packages/ts-svg-icons/src/lucide.generated.ts
55+
packages/ts-svg-icons/src/iconoir.generated.ts
56+
packages/ts-svg-icons/src/phosphor.generated.ts
57+
```
58+
59+
These files list names only. Collection websites can show icons that are newer than the version PureStack bundles, so treat the generated list as the source of truth. In TypeScript, the `AvailableIconNames` type autocompletes every name.
60+
61+
When a page uses a name that does not exist, the build continues. The icon is left out of the page, and the build logs `Unknown icon: <name>`. Check the build output after you change icon names. An unknown `favicon` name works differently and stops the build.
62+
63+
## Place an icon in a page
64+
65+
`Icon` renders one icon as an inline element. You can use it anywhere in MDX:
66+
67+
<Flex align="center">
68+
<Icon name="lucide:check" aria-hidden="true" />
69+
<span>Checks passed</span>
70+
</Flex>
71+
72+
```mdx
73+
<Flex align="center">
74+
<Icon name="lucide:check" aria-hidden="true" />
75+
<span>Checks passed</span>
76+
</Flex>
77+
```
78+
79+
### Size follows the text
80+
81+
An icon is 1.7 times the current font size and stays square. To resize it, change the font size of the icon or its container with a type class. You do not need to set a width:
82+
83+
<Flex wrap="true" align="center">
84+
<span class="fs-sm"><Icon name="tabler:bolt" aria-hidden="true" /></span>
85+
<span class="fs-body"><Icon name="tabler:bolt" aria-hidden="true" /></span>
86+
<span class="fs-h3"><Icon name="tabler:bolt" aria-hidden="true" /></span>
87+
<span class="fs-h1"><Icon name="tabler:bolt" aria-hidden="true" /></span>
88+
</Flex>
89+
90+
```mdx
91+
<span class="fs-sm"><Icon name="tabler:bolt" aria-hidden="true" /></span>
92+
<span class="fs-h1"><Icon name="tabler:bolt" aria-hidden="true" /></span>
93+
```
94+
95+
### Color follows the text
96+
97+
Icons are drawn in the current text color, so an icon matches the label beside it, including in dark mode. To give an icon its own color, choose a tone and apply `tone-text`. Use this to reinforce meaning, not to decorate:
98+
99+
<Flex wrap="true" align="center" class="fs-h3">
100+
<Icon name="tabler:circle-check" aria-hidden="true" class="tone--success tone-text" />
101+
<Icon name="tabler:info-circle" aria-hidden="true" class="tone--info tone-text" />
102+
<Icon name="tabler:alert-triangle" aria-hidden="true" class="tone--warning tone-text" />
103+
<Icon name="tabler:lock" aria-hidden="true" class="tone--danger tone-text" />
104+
</Flex>
105+
106+
```mdx
107+
<Icon name="tabler:circle-check" aria-hidden="true" class="tone--success tone-text" />
108+
<Icon name="tabler:alert-triangle" aria-hidden="true" class="tone--warning tone-text" />
109+
```
110+
111+
The [Semantic tones guide](/guides/semantic-tones/) explains the other tones and the `tone-*` classes.
112+
113+
### Make the meaning available to everyone
114+
115+
Every icon is hidden from assistive technology by default. Decide what a screen reader should announce in each case:
116+
117+
- **Next to visible text**, the text already gives the meaning. Add `aria-hidden="true"` to the icon so the icon stays hidden.
118+
- **On its own**, the icon has to carry the meaning. Put it inside an element with `role="img"` and an `aria-label` that describes the meaning, not the shape.
119+
- **As an action**, use a button, not a clickable icon. Read the [buttons and links](#buttons-and-links) section below.
120+
121+
A standalone icon with its meaning on the wrapper:
122+
123+
<span role="img" aria-label="Verified publisher" class="fs-h3 tone--success tone-text">
124+
<Icon name="tabler:shield-check" aria-hidden="true" />
125+
</span>
126+
127+
```mdx
128+
<span role="img" aria-label="Verified publisher" class="fs-h3 tone--success tone-text">
129+
<Icon name="tabler:shield-check" aria-hidden="true" />
130+
</span>
131+
```
132+
133+
## Frame an icon
134+
135+
`IconFrame` places an icon on a tone-colored tile. It works well as the lead symbol of a card, metric, or status summary. The tile takes `tone`, `variant`, and `size` (`sm`, `md`, or `lg`):
136+
137+
<Flex wrap="true" align="center">
138+
<IconFrame name="tabler:rocket" tone="accent" size="lg" aria-hidden="true" />
139+
<IconFrame name="tabler:shield-check" tone="success" variant="surface" size="lg" aria-hidden="true" />
140+
<IconFrame name="tabler:bell" tone="warning" variant="surface" size="lg" aria-hidden="true" />
141+
<IconFrame name="tabler:sparkles" tone="feature" variant="flat" size="lg" aria-hidden="true" />
142+
</Flex>
143+
144+
```mdx
145+
<IconFrame name="tabler:rocket" tone="accent" size="lg" aria-hidden="true" />
146+
<IconFrame name="tabler:shield-check" tone="success" variant="surface" size="lg" aria-hidden="true" />
147+
```
148+
149+
The [IconFrame reference](/components/layout/icon-frame/) lists every variant.
150+
151+
## Icons in components
152+
153+
Many components accept icon names as props and render them with `Icon`. You choose the symbol, and the component handles size, color, and spacing.
154+
155+
### Buttons and links
156+
157+
`Btn`, `BtnLink`, and `BtnGroup` accept `icon`. Set `iconPosition="end"` for icons that show direction:
158+
159+
<Flex wrap="true" align="center">
160+
<Btn tone="accent" icon="tabler:plus">Create project</Btn>
161+
<Btn tone="neutral" variant="surface" icon="tabler:arrow-right" iconPosition="end">Continue</Btn>
162+
<Btn tone="neutral" variant="surface" icon="tabler:settings" :iconOnly="true" ariaLabel="Open settings"/>
163+
</Flex>
164+
165+
```mdx
166+
<Btn tone="accent" icon="tabler:plus">Create project</Btn>
167+
<Btn tone="neutral" variant="surface" icon="tabler:arrow-right" iconPosition="end">Continue</Btn>
168+
<Btn tone="neutral" variant="surface" icon="tabler:settings" :iconOnly="true" ariaLabel="Open settings"/>
169+
```
170+
171+
An icon-only button always needs `ariaLabel`. The label is the button's accessible name, and screen readers announce it. Name the action, such as "Open settings", not the symbol. Use an icon-only button only when the symbol is widely recognized.
172+
173+
### Form fields
174+
175+
`FormInputField` places `icon` at the start of the field and `iconEnd` at the end. The icon adds a visual hint, so the field still needs its label:
176+
177+
```mdx
178+
<FormInputField id="email" label="Email" type="email" icon="lucide:mail" />
179+
```
180+
181+
Other components also take icon props, including `AlertBox`, `ExpandablePanel`, `Tabs`, the autocomplete inputs, `DropFiles`, the landing cards, and the pricing components. Each [component page](/components/) lists its icon props and their defaults.
182+
183+
## Navigation and site identity
184+
185+
### Navigation icons
186+
187+
Each page can set its own icon in the sidebar with frontmatter:
188+
189+
```yaml
190+
nav:
191+
order: 39
192+
icon: tabler:icons
193+
```
194+
195+
A folder's `_nav.json` can set icons for several entries at once. The keys are file names, folder names that end in `/`, or the `id` of a custom item:
196+
197+
```json
198+
{
199+
"icons": {
200+
"index.mdx": "tabler:home",
201+
"usage/": "tabler:book",
202+
"usage/transactions.mdx": "tabler:database",
203+
"github": "tabler:brand-github"
204+
},
205+
"items": [
206+
{ "id": "github", "title": "GitHub", "url": "https://github.com/example/project" }
207+
]
208+
}
209+
```
210+
211+
If both set an icon for the same page, the `_nav.json` entry wins over the frontmatter.
212+
213+
### Favicon and logo
214+
215+
In `siteConfig.json`, `favicon` and `logo.icon` take icon names:
216+
217+
```json
218+
{
219+
"favicon": "tabler:stack-2",
220+
"logo": {
221+
"brand": "Acme",
222+
"icon": "tabler:stack-2"
223+
}
224+
}
225+
```
226+
227+
The build turns `favicon` into `assets/favicon.svg` and colors it with your theme's dark accent. A favicon must be clear at 16 pixels, so choose a simple, bold shape. The [site configuration guide](/guides/site-config/) covers the other logo settings.
228+
229+
## Icons in browser apps
230+
231+
The steps above apply to pages that the build renders. An app that you mount with [RegorApp](/components/runtime/regor-app/) runs in the browser. There, the full icon list would be too large to send, so each app registers only the icons it uses.
232+
233+
Every icon is also available as a named export. To get the export name, take the icon name and replace the colon and every hyphen with an underscore. For example, `tabler:plus` becomes `tabler_plus`, and `tabler:heart-filled` becomes `tabler_heart_filled`. Import the exports you need, map them back to their names, and give `defineIconComponents` a function that looks the icons up:
234+
235+
```ts
236+
import {
237+
defineButtonComponents,
238+
defineFormSelectField,
239+
defineIconComponents,
240+
} from '@purestack/ts-components'
241+
import {
242+
lucide_chevron_down,
243+
tabler_arrow_right,
244+
tabler_plus,
245+
} from '@purestack/ts-svg-icons'
246+
import { createApp, html } from 'regor'
247+
248+
const icons: Record<string, string> = {
249+
'lucide:chevron-down': lucide_chevron_down,
250+
'tabler:arrow-right': tabler_arrow_right,
251+
'tabler:plus': tabler_plus,
252+
}
253+
254+
createApp(
255+
{
256+
components: {
257+
...defineButtonComponents(),
258+
...defineFormSelectField(),
259+
...defineIconComponents((name) => {
260+
if (!icons[name]) throw new Error(`Icon is not registered: ${name}`)
261+
return icons[name]
262+
}),
263+
},
264+
},
265+
{ selector: 'app#project-app', template: html`<ProjectApp/>` },
266+
)
267+
```
268+
269+
Bundlers include only the icons you import. Do not import `getSvgIcon` in browser code, because it includes every icon in the bundle.
270+
271+
The lookup function throws for an unregistered name, so a missing icon causes an error during development instead of leaving an empty space. Register the icons that you pass to components and the default icons that components draw themselves. If you leave a default unregistered, that part of the component appears without its icon:
272+
273+
| Component | Default icons |
274+
| --- | --- |
275+
| `FormSelectField`, `BtnGroup` drop-down | `lucide:chevron-down` |
276+
| Autocomplete inputs | `lucide:chevron-down`, `lucide:loader-circle` while loading, `lucide:x` to remove a choice |
277+
| `DropFiles` | `lucide:cloud-upload`, `lucide:file`, `lucide:trash-2`, and `lucide:file-image`, `lucide:file-text`, or `lucide:file-archive` for common file types |
278+
| `SignIn` | `lucide:log-in`, `tabler:user-filled` |
279+
| Comparison and pricing features | `lucide:check`, `iconoir:check` |
280+
| `Composer` toolbar | `lucide:bold`, `lucide:italic`, `lucide:underline`, `tabler:align-left`, `tabler:align-center`, `tabler:align-right`, `lucide:list`, `lucide:list-ordered`, `lucide:link`, `lucide:eraser`, `lucide:code` |
281+
282+
Setting your own icon prop replaces the default, so you only need to register the icon that you set.

‎frontend/purestack.studio/guides/index.mdx‎

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
---
22
title: Guides
3-
description: Learn how to build and style a PureStack site with the CLI, site configuration, VS Code extension, Regor MDX, typography, utility classes, semantic tones, and themes.
3+
description: Learn how to build and style a PureStack site with the CLI, site configuration, VS Code extension, Regor MDX, typography, utility classes, semantic tones, icons, and themes.
44
template: doc
55
layout:
66
showNav: true
@@ -51,6 +51,11 @@ These guides follow the path from a content folder to a finished interface. Star
5151
<p>Compare all ten tones, see their surface and button roles, and try interactive states.</p>
5252
<BtnLink href="/guides/semantic-tones/" tone="accent" variant="link">Explore tones</BtnLink>
5353
</Panel>
54+
<Panel tone="neutral" bodyClass="p-3">
55+
<h2 class="mt-0">Icons</h2>
56+
<p>Find icon names, size and color icons with the text, and register them in browser apps.</p>
57+
<BtnLink href="/guides/icons/" tone="accent" variant="link">Add icons</BtnLink>
58+
</Panel>
5459
<Panel tone="neutral" bodyClass="p-3">
5560
<h2 class="mt-0">Themes</h2>
5661
<p>Start with a skin, tune light and dark palettes, and add theme-aware styles.</p>

0 commit comments

Comments
 (0)