P-03 · Primitives · Primitives
All elements · Icon on the Primitives page
Last checked 2026-10-22 · Awaiting approval · Accessibility tests
Approve Icon
Approving as
One SVG glyph from the sprite. Strictly non-interactive (pointer-events: none): wrap it in
a Pressable or Button to make it clickable. Default 20px, 24px on mobile. Never size the inner glyph;
size the wrapper.
Glyph families. Tabler (sprites/icons.svg) everywhere.
Editor exception: the QEditor editors use Fluent UI System Icons,
Regular style only (sprites/office-icons.svg), inside
.qf-office and qf-office__*
elements and in the office catalog sections. The two families never mix, and office symbols carry
stroke="none" so this primitive's stroke never outlines them. Both rules are gated; see the
README's "Icons" section.
| Axis | Values |
|---|---|
data-size |
inherit · xs (14) · sm (16) · md (20) · lg (24) · xl (32) |
data-color |
inherit · primary · info · muted · success · warning · error · purple · electric-blue · pink · yellow · brown · inverse |
data-spin · data-pulse |
loading animations |
Guidance
When to use
- Add a glyph beside a label in a button, menu item, list row or nav item.
- Mark status next to text, such as a check for done or a triangle for a warning.
- Show a loading glyph with
data-spininside a button or row while work runs. -
Use Fluent Regular glyphs from
sprites/office-icons.svginside.qf-officeeditors, and Tabler everywhere else.
When to consider something else
-
When the glyph must be clicked, wrap it in a Pressable or Button; Icon itself has
pointer-events: none. See Pressable. - When the glyph stands for a person, use Avatar. See Avatar.
- When the user waits for a longer task with measurable progress, use Progress instead of a spinning icon. See Progress.
- When content is loading in place, use Skeleton rather than a spinner over the region. See Skeleton.
Usability
- Pair icons with visible text wherever space allows; an icon-only control needs a Tooltip with its name.
- Size the wrapper or use
data-size; never size the inner glyph. -
Use
data-size="inherit"anddata-color="inherit"to match the surrounding text. - Keep one glyph per meaning across the app.
- Don't mix Tabler and Fluent glyphs; Fluent stays inside the editor scope, Tabler stays outside it.
- Don't rely on icon color alone to show status.
-
Don't leave
data-pulseordata-spinrunning after the work is done.
Accessibility
Keyboard
No keyboard interaction of its own.
Roles and names
-
A decorative icon, or one next to text that says the same thing, carries
aria-hidden="true", as every specimen does. -
An icon that is the only content of a control is hidden, and the control carries the name with
aria-label. -
A meaningful standalone icon (a status glyph with no text) needs
role="img"and anaria-label, or adjacentqf-sr-onlytext.
Focus
- Icon is never focusable. Focus belongs to the Pressable or Button that wraps it.
Other requirements
- Icon is non-interactive and has no keyboard behavior of its own.
-
Glyphs that convey information need 3:1 contrast against their background (1.4.11); check
muted,yellowand accent colors in both themes. - Status must not depend on color alone (1.4.1).
-
data-spinanddata-pulseare reduced to a single near-instant run underprefers-reduced-motion(2.3.3); a loading state still needs a text or live-region announcement (4.1.3).
Test your implementation too. This guidance covers the element as specified here. How a screen composes it (labels, order, announcements) still needs its own keyboard and screen reader check.
Accessibility tests
Passed the gate (run 358b316 on 2026-10-22): no finding in any theme. Awaiting a person’s approval of the checklist below.
| Area | Automated | A person checks |
|---|---|---|
| General | Passed | Color is never the only cue, text alternatives say the same thing as the graphic, and the element reads the same in the light and the dark theme. |
| Zoom | Passed | At 200% zoom, at 320 CSS pixels wide and with the WCAG text-spacing overrides, nothing is lost, clipped or overlapping. |
| Keyboard | Passed | Every row of the Keyboard table works, focus is always visible and never trapped, and the tab order follows the reading order. |
| Screen reader | Passed | With NVDA or JAWS on Windows and VoiceOver on macOS, names, roles and states are announced as Roles and names says, and changes are announced once. (3 items) |
Approving. When the checklist passes on the checked run, record it with
bun run a11y:approve -- icon --by "Your name" and commit
a11y/approvals.json with the rebuilt pages.
All test status
Package
Consumers link one stylesheet, css/classic/index.css, which includes every element. The
files below are where this element is defined.
Latest updates
| Date | Version | Breaking | Description |
|---|---|---|---|
| 2026-10-02 | 0.1 | No | Editor icon exception — Fluent Regular only, scoped to the editor |
| 2026-10-02 | 0.1 | No | Office symbols reset stroke so .qf-icon no longer outlines them |
| 2026-10-02 | 0.1 | No | Inline every icon sprite, not only icons.svg |
| 2026-10-02 | 0.1 | No | Office icon list and a --check that fails on missing glyphs |
| 2026-09-30 | 0.1 | No | Nav (both skins): D-9's icon is drawn without .qf-icon's stroke, so circle-plus-filled keeps its plus; the sprite stays as provided (QF-10570) |
| 2026-09-30 | 0.1 | No | Revert "sprites: a -filled glyph sets stroke="none", so .qf-icon's inherited stroke no longer closes its cut-outs (QF-10570)" |
| 2026-09-30 | 0.1 | No | A -filled glyph sets stroke="none", so .qf-icon's inherited stroke no longer closes its cut-outs (QF-10570) |
| 2026-09-26 | 0.1 | No | Classic dark: surfaces, ink and borders from the info scale; nav icons at full strength (QF-10566) |
| 2026-09-15 | 0.1 | No | Utility links: icons with tooltips when labels overflow, one-row masthead, ellipsis menu at 30rem |
| 2026-09-15 | 0.1 | No | Skin toggle icons |