GrIcon
Renders an icon as a standalone interface element.
Overview
When to take it
- the icon needs a uniform size —
--gr-icon-sizeinstead of a width and a height in every place; - the icon takes its colour from the palette —
toneinstead of afillof your own; - the icon is meaningful —
labelgives it a name; without one it is hidden from a screen reader, and that is right by default; - the icon rotates —
spinfor a waiting state with no animation of your own.
When to take something else
| Need | Take |
|---|---|
| An icon inside a button | GrButton with square |
| The avatar of a person | GrAvatar |
| A key on the keyboard | GrKbd |
| A waiting indicator | GrLoading |
The component does not draw the icon itself — it arrives through a slot from any set
(unplugin-icons, an SVG of your own, a sprite). The package does not ship an icon set of
its own and will not: duplicating a delivery is harmful.
Variants every tone and view, rendered by the real component
tone
primaryneutralsuccesswarningdangerinfoslateazure
Sizes
xssmmdlg
Playground controls come from the API, not from hand
Loading…
0 changed· state is in the URL
<GrIcon><i class="i-lucide-bell" /></GrIcon>Examples 4 each with a live demo and its code
Decorative next to a word, named when alone
Next to its word an icon repeats it, so by default it is hidden from the screen reader — no
aria-hiddento remember. Alone in a cell it is the message, andlabelgives itrole="img"and a name. The glyph comes from anywhere: the icon set or an SVG of your own.- PasswordChanged 3 months ago
- Two-factor authenticationAuthenticator app
- Active sessions3 devices
Member 2FA Email Ada Lin Grace Wu Linus Ray A tone for the meaning, the parent’s colour by default
tonepaints the glyph with a-textrole, so a warning triangle stays readable on the page background. Without it the icon iscurrentand follows its parent — the arrow turns into a link together with the link.- Deploy of api-gateway finished2 min
- Disk usage on db-2 passed 85 %14 min
- Payment webhook returned 502 three times31 min
- Maintenance window starts at 02:00 UTC1 h
- Grace Wu opened #41822 h
- Project «Q2 launch» archivedyesterday
Sizes tied to the text
Each step of
sizematches a step of the text it stands next to, and the scale lives in--gr-icon-size-*tokens. A number is the escape hatch for one-off art, and a toolbar sets the size once throughGrConfigProvider.- Workspace settings
lg - Billing and invoices
md - Next invoice on 1 November
sm - Prices exclude VAT
xs
No messages:size="48"- Workspace settings
A spinner that names its state
spinturns the glyph for work in progress, and underprefers-reduced-motionthe base clamp stops it upright rather than at a random angle. Each status is the icon alone, so each carries alabel.- Install dependencies42 s
- Type check and lint1 min 08 s
- Unit tests4 812 of 5 947
- Build packageswaiting
- Publish to npmwaiting
API generated from the package · cannot drift
Props
| Prop | Type | default | Description |
|---|---|---|---|
tone | GrIconTone | undefined | "current" | A colour from the palette. `current` — inherit the text colour of the parent. |
size | number | "xs" | "sm" | "md" | "lg" | undefined | undefined | The size by the scale of the package or an arbitrary one in pixels. |
label | string | undefined | undefined | The name of a meaningful icon. If it is set, the icon is declared `role="img"` and stops being hidden. Unset — the icon is decorative. |
spin | boolean | undefined | false | Rotation — for spinners. |
Slots
| Slot | Type | Description |
|---|---|---|
default | any | Markup of your own for the icon instead of a name from a set. |
Types
Every exported type of the subpath, grouped by the file it lives in. Names, generics and bodies come from the package’s declaration files and cannot drift.
import type { GrIconSize, GrIconTone, GrIconConfigurableProps } from '@feugene/granularity/components/GrIcon'Filedist/types/components/GrIcon/config.d.ts
const grIconConfig: import("@feugene/granum/contract").GranumComponentDescriptor<"GrIcon">;Filedist/types/components/GrIcon/safelist.d.ts
const grIconSafelist: string[];Filedist/types/components/GrIcon/grIconStyles.d.ts
type GrIconSize = GrComponentSize;type GrIconTone = GrTone | 'current';Filedist/types/components/GrIcon/defaults.d.ts
interface GrIconConfigurableProps {
size: GrComponentSize;
}Filedist/types/components/GrIcon/GrIcon.vue.d.ts
interface GrIconProps {
size?: GrIconSize | number;
label?: string;
tone?: GrIconTone;
spin?: boolean;
}Usage from the library prose
Decorative and meaningful
By default an icon is decorative: the component sets aria-hidden="true" itself. In the
library an icon almost always accompanies text, and the attribute used to be written by hand
at every call site — forget it once and a screen reader reads the <title> from the SVG.
label makes an icon meaningful: role="img" and aria-label appear, and the hiding is
removed.
<!-- the text "Saved" is next to it — the icon is decorative -->
<GrIcon>
<IconCheck />
</GrIcon>
<!-- the icon carries the meaning itself -->
<GrIcon label="Verified">
<IconCheck />
</GrIcon>The rare case of “the icon is meaningful, but the name is given by a neighbouring element” is expressed with an ordinary attribute: a fallthrough is stronger than the component’s own binding.
The size
size is the scale of the package (xs | sm | md | lg, md by default) or an arbitrary
number in pixels. The scale is read from GrConfigProvider, and a number is a local escape
hatch past the config.
The values of the scale live as the --gr-icon-size-xs…lg tokens, so the size of the icons
is configured by the theme rather than by rebuilding the package. The --gr-icon-size
variable itself is a point of customisation in place: both the wrapper and the nested SVG
compute themselves from it.
The tone and the rotation
tone colours the icon with a text token (current by default — the colour is inherited
from the parent). A saturated tone as a text colour is forbidden in the package: on a light
background it gives a contrast of about 2:1, so the -text roles are used, as in GrAlert.
spin switches the rotation on for spinners. The component needs no separate support for
prefers-reduced-motion: the global clamp in base.css holds the animations, and the
rotation freezes in its starting position rather than at a random angle.
The style is deliberately global
The .gr-icon rule is not scoped: the SVG arrives through the consumer’s slot, and a scoped
style does not reach it — a :deep would have to be written at every call site.
Props in this example