ComponentsData & statusIcon
On this page
Data & status

Icon released

An icon from any set — a font, SVG drawings as data, a sprite, your own component — in a box the text sizes and colours; one line registers the set and dresses the library in it.

  • Import@pacit/components/icon
  • Selectorpct-icon

No APG pattern applies, and none is invented — the reasoning is under Accessibility ↓

icon.demo.ts — the same file runs here and is highlighted under Code

Start here

Usage

One import per component, and the shortest possible use — the package itself is installed once, on the start page.

Import this component
import { PctIcon } from '@pacit/components/icon';
ExportedWhat it is
PctIconNametype| 'calendar' | 'check' | 'chevron-down' | 'clock' | 'close' | 'danger' | 'indeterminate' | 'info' | 'success' | 'user' | 'warning'The icons the library draws, by the ROLE they play rather than by what they look like (req-api-icons). chevron-down and not arrow-down-16: a consumer swapping it is answering "what does the select's trigger show", not "which of my 16-pixel arrows".
PctIconSetOrSourcetypeunreleasedType<unknown> | PctIconSourceWhat providePctIcons takes, any number of times: a set component, or a source.
PCT_ICONStokenONE set component, as 0028 wrote it — kept in that shape so that whoever read or provided it keeps compiling and running. A set standing here is read first, before everything PCT_ICON_SOURCES carries.
PCT_ICON_SOURCEStokenunreleasedThe consumer's icons, in order of precedence: set components and sources alike — what providePctIcons(…) provides (0083). A source holds no markup at all, which is why it can be a plain object.
providePctIconsfunctionRegisters the application's icons, for the whole application or for one subtree (req-api-icons). Every argument is a set component or a source, asked in this order: the first that answers draws. A role no argument answers keeps the drawing the component ships with, the same partial override as providePctTexts.
PctIconTemplatedirectiveng-template[pctIcon]One icon of a set: an <ng-template> under the name it replaces (req-api-icons).
PctIconcomponentpct-iconAn icon: a drawing in a box the text around it sizes and colours (req-api-icons).
PctIconIdsinterface
PctIconIdtypeunreleased[keyof PctIconIds] extends [never] ? string : keyof PctIconIds & stringWhat icon takes: every key of PctIconIds, or any string while that interface is empty.
PctIconTemplateRenderinginterfaceunreleasedA drawing a set component registered under a role: the template renders in the box.
PctIconFontRenderinginterfaceunreleasedA glyph of an icon font: a <span> wearing the font's classes, holding the ligature text when the font reads one (Material), with font-variation-settings or whatever else the font takes as inline style.
PctIconComponentRenderinginterfaceunreleasedA component created in the box with the inputs given — the way every drawing that is not a template and not a font arrives, including the library's own SVG renderer.
PctIconRenderingtypeunreleasedPctIconTemplateRendering | PctIconFontRendering | PctIconComponentRenderingWhat a source answers for an id: a template, a font glyph or a component — data, never markup. A string of SVG has no place here on purpose: Angular's sanitizer deletes an <svg> from [innerHTML] outright, and the only way around it is the library trusting markup a consumer wrote, which is the shape 0028 refused.
PctIconSourceinterfaceunreleasedA source of icons: answers an id with a rendering, or with null so that the next source is asked. providePctIcons(…) takes any number of them, in order of precedence.
PctSvgNodetypeunreleasedreadonly [ tag: string, attributes: Readonly<Record<string, string | number | undefined>>, ]One element of an SVG drawing given as data: the tag and its attributes.
PctSvgIconDatainterfaceunreleasedAn SVG drawing as data: the box it is drawn in, the attributes of the root and the elements inside it.
PctSvgIconDefinitioninterfaceunreleasedAn icon as FontAwesome's SVG packages export it: faHouse from @fortawesome/free-solid-svg-icons. Only the icon tuple is read — the width, the height and the path data, one path or two.
PctSvgIconInputtypeunreleasedPctSvgIconData | readonly PctSvgNode[] | PctSvgIconDefinitionWhat svgIcons() takes under an id: a drawing as data, the nodes alone (Tabler and the packages built like it — drawn on the 24-grid with their stroke conventions), or a FontAwesome definition.
PctIconRolestypeunreleasedReadonly< Partial<Record<PctIconName, string | undefined>> >The library's roles in a font's vocabulary — what an adapter carries so that one line dresses every component, and what a consumer overrides when their version spells one differently (times became xmark between FontAwesome 5 and 6).
PctIconRoleSelectiontypeunreleasedPctIconRoles | readonly PctIconName[]Which of the library's roles a font dresses, said one of two ways. A map corrects an adapter's own — { close: 'times' } for a version that spells one differently, undefined to keep the library's drawing for that one role — and the rest of the map stands; a font of your own has no map of its own, and takes the map as it is. A list names the roles to dress and nothing else, each in the adapter's spelling: ['chevron-down'] dresses the select's arrow and leaves every other component as it ships, [] dresses none — the font draws your ids, and the library draws its own.
PctIconFontRolesinterfaceunreleasedWhat every font adapter takes besides its own knobs.
PctIconFontOptionsinterfaceunreleasedHow an icon font keys its glyphs, for a font this entrypoint has no adapter of its own for — or for the application's own.
iconFontfunctionunreleasedAn icon font as a source: every id is a glyph of it (req-api-icons).
PctFontAwesomeOptionsinterfaceunreleasedWhat FontAwesome's stylesheet is told, besides the roles.
fontAwesomefunctionunreleasedFontAwesome, as classes: <pct-icon icon="house"> is <i class="fa-solid fa-house">.
primeIconsfunctionunreleasedPrimeIcons, as classes: <pct-icon icon="bell"> is <i class="pi pi-bell">.
bootstrapIconsfunctionunreleasedBootstrap Icons, as classes: <pct-icon icon="bell"> is <i class="bi bi-bell">.
PctMaterialIconsVarianttypeunreleased'filled' | 'outlined' | 'round' | 'sharp' | 'two-tone'Which of the Material Icons fonts is loaded.
PctMaterialIconsOptionsinterfaceunreleasedWhat the Material Icons font is told, besides the roles.
materialIconsfunctionunreleasedMaterial Icons, the ligature font: <pct-icon icon="home"> is <span class="material-icons">home</span>.
PctMaterialSymbolsVarianttypeunreleased'outlined' | 'rounded' | 'sharp'Which of the Material Symbols fonts is loaded.
PctMaterialSymbolsOptionsinterfaceunreleasedWhat the Material Symbols font is told, besides the roles: the axes of a variable font. An axis left out is left to the stylesheet.
materialSymbolsfunctionunreleasedMaterial Symbols, the variable ligature font: <pct-icon icon="home"> is <span class="material-symbols-outlined">home</span>, with font-variation-settings for the axes given.
<pct-icon icon="trash" tone="danger" />
Examples

Every state, running

Each example is the component itself, not a screenshot. Open the code under any of them.

PrimeIcons

primeIcons() is the adapter: <pct-icon icon="bell"> holds a span wearing pi pi-bell, and PrimeIcons' own stylesheet — yours to include, in the styles of angular.json — draws the glyph. The library brings no font and no list of names: whatever the stylesheet knows is an id here. The same line carries the library's eleven roles in PrimeIcons' spelling, so the select's arrow and the toast's marks wear the set too — the example "One line dresses the library" shows that side, and "Which roles a set dresses" how to keep some of them.

Material Icons

A ligature font: the text of the span is the id — home — and the font draws the word as one glyph. materialIcons() writes both, the class and the text, and the box hides the word from a screen reader so that a named icon is not read twice. variant names the font loaded — outlined here, the material-icons-outlined class of the stylesheet. Material Symbols, the variable font, is materialSymbols() with its axes.

Font Awesome

fontAwesome() is the free solid style: <pct-icon icon="heart"> holds a span wearing fa-solid fa-heart, drawn by the stylesheet you include. style picks another of the font's — regular in the second row, brands for the logos — and a version-5 sheet takes its three-letter prefix in prefix. The roles map spells the names of versions 6 and 7; a sheet that spells one differently corrects it in roles, which is what { close: 'times' } is for on version 5.

Your own SVG

A drawing is data — a viewBox, the root's paint and the elements inside it — rendered one element at a time, so no string of markup is ever trusted. svgIcons() registers drawings under the ids you choose. Written out, a drawing names its own grid and paint, like the mark here; a bare node list is read on the 24-grid with a round 2-unit stroke, the shape lucide and Tabler export, so their icons drop in as they are; a definition from @fortawesome/free-solid-svg-icons does too. A one-off goes into the box as content with no source at all, and a folder of files a designer exported becomes svgSprite('icons.svg').

Your own icon font

An icon font is a class per glyph and a stylesheet that draws it, and that is all iconFont() asks for: the classes an id becomes. A font that reads a ligature adds text, a variable font its axes in style, and roles names the library's roles in the font's spelling. The four rules under this stage stand in for a @font-face and its glyph classes — the same mechanism PrimeIcons and Font Awesome run on.

Several sets at once

The arguments of providePctIcons() are asked in order, and the first that answers draws. Drawings answer for their ids and null for the rest, so they stand first; a font answers for every id, so it stands last — and a second font behind it would never be reached. A prefix makes room for one: brand:github goes to Font Awesome's brands, and everything the drawings do not know goes to PrimeIcons.

One line dresses the library

A source that knows the library's roles in its own vocabulary swaps the drawings every component ships with: providePctIcons(primeIcons()) on this card, and the avatar's silhouette and the checkbox's tick are PrimeIcons' — pi pi-user, pi pi-check. It is an ordinary provider, so the same line at bootstrap dresses the whole application, and here it stops at the edge of the card: the pair on the right draws what the components ship.

Which roles a set dresses

The roles of a source are what dresses the library, and they are yours to choose. A list names the roles to dress and nothing else, each in the adapter's spelling: [] is a font that draws your ids and leaves every component as it ships, ['chevron-down'] is the select's arrow and nothing else. A map corrects the adapter's own, and a role set to undefined in it is handed back to the library — Material everywhere but the checkbox's tick. primeIcons({ roles: [] }), fontAwesome() and the others take roles the same way; svgIcons() takes the map.

No roles: nothing of the library's replaced
One role: the arrow, and nothing else
The adapter's map, less the tick

Tones and sizes

The four tones are PctTone, the same list the toast, the badge and the button read; an icon wearing one takes the skin's colour and nothing else changes. The three sizes are steps on the icon's own tokens, and no size is 1em — the text's own, the right size beside a word. The drawings are lucide's, rendered from the data the package exports.

Inbox
API

What it reads, and what it writes back

Read from the source at build time — the types, the defaults and the lines of documentation are the library's own.

unreleased marks 31 of them: on main, and not in @pacit/components@0.2.0 — the next release names the version.

An icon: a drawing in a box the text around it sizes and colours (req-api-icons).

Inputs

NameTypeWhat it does
namedefault nullPctIconName | nullWhich ROLE this icon plays in a component of the library. A component always names one; a consumer dropping a one-off drawing into their own markup leaves it out, and then the content is all there is. Resolved through the roles of each source, in order.
iconunreleaseddefault nullPctIconId | nullWhich icon of the CONSUMER's set this is — house, trash, whatever the sources answer for. string until the application augments PctIconIds, and then exactly its keys. Not for a component of the library: those name a role.
toneunreleaseddefault nullPctTone | nullThe colour of the skin's four tones, or null for the colour of the text around it. An icon IS the second channel a tone needs — a shape that stays when the colour goes — so a tone here never speaks alone; what it cannot do is tell two states apart by colour on one and the same glyph (0076).
sizeunreleaseddefault nullPctSize | nullA step on the icon's own scale, or null for the text's own size (1em) — the default, and the right one beside text. The steps are tokens, so a theme moves them (req-token-override); any other size is font-size on the box.
labelunreleaseddefault nullstring | nullThe accessible name, for an icon that carries information nothing else in the page says. With one the element is role="img" and read by that name; without one it is hidden from the accessibility tree, which is right for every icon that stands beside the words it repeats.

Outputs

None of its own. The native events — (click), (focus), the form's submit — are the platform's, and two-way models above carry the rest.

What it writes on the element

The hooks a stylesheet or a test can hold. Stable by contract — the parts inventory gate fails the build when one disappears.

AttributeBound toNote
classpct-icon
aria-hiddennamed() === null ? "true" : nullDecoration unless it is given a name: beside the text it belongs to — the select's arrow next to the chosen value, the checkbox's tick inside a labelled control — a second reading of the same thing is noise in a screen reader. Named, it is an image.
rolenamed() === null ? null : "img"
aria-labelnamed()
data-pct-tonetone()
data-pct-sizesize()
Styling

Parts to select, tokens to override

Two ways in, both stable. No class names to depend on.

Parts

In a test, PctIconHarness from @pacit/components/testing reads a part by the name below and a state by its attribute — the harness gate holds the names to the package.

No parts of its own — the element you write is the whole of it.

Tokens

Every value the component reads, with the light and the dark default. Override a token at any scope and it cascades like any custom property.

TokenStyles
--pct-icon-fg-danger{pct.danger}#b91c1c · #f87171Colour of an icon wearing the danger tone.
--pct-icon-fg-info{pct.info}#2563eb · #60a5faColour of an icon wearing the info tone.
--pct-icon-fg-success{pct.success}#15803d · #4ade80Colour of an icon wearing the success tone.
--pct-icon-fg-warning{pct.warning}#b45309 · #fbbf24Colour of an icon wearing the warning tone.
--pct-icon-size20pxSize of the icon's box at the base step.
--pct-icon-size-lg24pxSize of the icon's box at the large step.
--pct-icon-size-sm16pxSize of the icon's box at the small step.

Theming

A brand override in a few declarations. Scope it to a theme attribute, a page or one container — the component never notices.

[data-theme='brand'] {
  --pct-icon-fg-danger: #9f1239;
  --pct-icon-size-lg: 28px;
}
Accessibility

Measured, not assumed

Pattern: none — an icon is decoration beside the words it repeats, out of the accessibility tree, and an image (role="img") only when it is given a label; neither has a pattern. Named in the class JSDoc.

Keyboard

keyeffecttest

Empty by design: an icon takes no focus and handles no key — the platform provides nothing here because there is nothing to provide (req-api-platform).

What is measured, and what is not yet

Every line is a test in three engines or a gate on every commit. The gaps are listed the same way, each with the requirement that will close it.

MeasuredARIA pattern named in the class JSDoclibs/components/icon/src/icon.ts
MeasuredKeyboard map tested key by keythe empty map is the claim — nothing here is focusable; tools/check-aria.mjs point 8 holds that a hidden host holds nothing a user can land on
Measuredaxe audit on the component's own sandbox viewapps/sandbox-e2e/src/a11y.spec.ts — /icon in SBX_ROUTES
MeasuredVisual screenshotapps/sandbox-e2e/src/visual.spec.ts — icon-tones and icon-tones-rtl
Measuredforced-colors: active — no state carried by colour aloneapps/sandbox-e2e/src/forced-colors.spec.ts › "a toned icon arrives at the palette's word for text, with its drawing standing" — the sheet writes no forced-colours rule at all: the mode forces color itself and the drawing is what is left, which is the whole reason an icon may wear a tone (0076)
n/aprefers-reduced-motion — duration from a tokennot applicable — no motion at all
n/aTouch target ≥ 24×24 px outrightnot applicable — nothing here is a target; an icon-only button is a button, measured there
MeasuredSize axis aligned to --pct-control-height-*deliberately not: an icon is not a control and size steps the icon's OWN tokens — apps/sandbox-e2e/src/icon.spec.ts › "the box is the text's own size, and each step is the icon's own token" measures 1em beside text and the three steps against the tokens
GapDensity axisnone — gap: the same one every component here has (req-token-density)
MeasuredRTL — no physical properties + a dir="rtl" screenshotlibs/components/icon/src/icon.scss — a square box and a flex centre, nothing directional. The screenshot: icon-tones-rtl
MeasuredSSR + hydration with no NG05xxapps/sandbox-e2e/src/hydration.spec.ts — /icon in SBX_ROUTES; every kind of rendering is a template or a component the framework creates, so the server and the client build the same tree
n/aFormsnot applicable — an icon holds no value a form owns
By designParts registered in the inventorynone — deliberately: the host is the box and nothing inside it may carry a part; tools/check-icons.mjs point 6 refuses one, tools/check-parts.mjs counts zero and the README table carries both entrypoints
MeasuredTokens registered + an entry in contrast.policy.jsonlibs/tokens/src/contrast.policy.json — four entries, one per tone against the page (UI error); libs/tokens/tokens.snapshot.md
n/aStrings through PCT_TEXTSnot applicable — the component draws no text of its own; tools/check-texts.mjs point 3 reads the ligature text as a binding and the two dev-mode warnings as point 6's
MeasuredEntrypoint size budgetlibs/components/size.snapshot.md — ./icon grew by the sources, the tones and the sizes, and the fourteen entrypoints that imported it then moved with it in one visible diff; ./svg-icon is a row of its own so that none of them carries the renderer (lesson-248)
MeasuredScreen-reader logRead 2026-10-02 from docs/acr/at/ — three readers, whole sandbox. /icon's only stops of its own are the two checkboxes of the dressed pair (check box checked in Orca, check box, checked in NVDA, checked checkbox in VoiceOver), and no icon is a stop in any of the three — which is the claim: hidden until named, and the named one is an image a reader meets in browse mode, not on Tab
MeasuredA docs page with live examplesapps/sandbox/src/app/views/icon/ (the sandbox view, with the sandbox's own drawings and a six-glyph font of its own). The published site: /components/icon — prerendered, with lucide's drawings and the stylesheets of PrimeIcons, Font Awesome and Material Icons behind the adapters; nine examples — the three fonts, a drawing and a font of your own, several sets at once, the library dressed whole and in part, the tones
MeasuredUnit + mutationlibs/components/icon/src/icon.spec.ts, libs/components/icon/src/fonts.spec.ts, libs/components/svg-icon/src/svg-icon.spec.ts — the sources in order, the roles, the box's attributes, the two warnings, every adapter's whole roles map, every tag the renderer knows and every attribute it refuses; libs/components/mutation.snapshot.md — icon.ts, fonts.ts and svg-icon.ts measured
Evidence

What the repository holds for this component

Read at build time from the tracked snapshots, not typed here.

93.28%mutation score111 of 119 mutants killed
6 × 3e2e cases × enginesplus the shared audits: axe, forced colors, preferences, size, hydration
4colour pairs measuredlight and dark, at every token build
2visual baselinesChromium, the sandbox's own pictures
27elements in the preview0 listeners · 1 render to settle · 11047 µs in jsdom, held exactly but for the clock

Known limitations

  • No string of SVG, and no URL to fetch. A consumer holding raw <svg> markup — iconify's body, a designer's files read as text — has three roads: a sprite (svgSprite), a set component with the markup in its templates (0028), or the content of the box. A markup kind taking a SafeHtml the consumer trusted, and a url kind fetched once, are the next two kinds if they are asked for; neither is a reason to take a plain string.
  • Multicolour icons render as drawn. tone and the box's colour reach a drawing through currentColor; a drawing that paints its own colours — a brand mark, a duotone icon's second layer — keeps them and ignores the tone, as it should.
  • A tone cannot tell two states apart on one glyph. A red check and a green check mean two things by colour alone, and no gate sees it; the contract is prose, and the icon's own shape is what the tone may lean on (0076).
  • The adapters know one version of each set. FontAwesome 6 spells xmark where 5 spelled times; the roles map and the prefix are overrides for exactly that, and the version each map assumes is written beside it.
  • No color input. A custom colour is color on the box, or --pct-icon-fg-<tone> on a scope: a style in a template is what the token tiers exist to avoid.