Structural icons are not the icons you pass to kui-icon by name. They never touch the icon registry or the network, they render on the server, and you can replace them for the whole app or for one subtree without changing a template.

import type { KuiIconGlyph } from '@kikita-labs/ui';

export const CIRCLE_X: KuiIconGlyph = {
  node: [
    ['circle', { cx: 12, cy: 12, r: 10 }],
    ['path', { d: 'm15 9-6 6' }],
    ['path', { d: 'm9 9 6 6' }],
  ],
};

A role changes every component without a more specific override; a component slot changes only that component.

  • Order: defaults.<component>.<slot>Icon, then defaults.icons.<role>, then the built-in glyph.
  • Levels merge per key like every other default, so a nested level overrides only the roles it names. Values may be plain glyphs or signals.

defaults.icons takes one glyph per role. Roles are named for what the icon does, not for its shape: one cross is close on a dialog, remove on a chip and clear in a field.

NameTypeDefaultDescription
closerole — Dialog, Drawer, Media Viewer, Toast and Alert close buttons.
removerole — Chip remove button (also the chips of a multiple Select) and File Upload remove button.
clearrole — Clear button of Select, Combobox, Date Picker and Time Picker, and the Command Palette search clear.
pickerChevronrole — Options toggle of Select, Combobox, Date Picker, Time Picker and Color Input.
previous, nextrole — Previous and next page, month, slide or tab group (Pagination, Calendar, Calendar Range, Carousel, Tabs, Media Viewer).
first, lastrole — First and last page buttons of Pagination.
disclosurerole — Expand control of Accordion items and Tree nodes; the rotation stays CSS.
separatorrole — Breadcrumb separator.
checkrole — Completed Stepper step and finished File Upload item.
statusInfo, statusSuccess, statusWarning, statusDangerrole — Status mark of Alert and Toast, and the warning mark of a non-default Confirm dialog.
externalLinkrole — Mark after an external kuiLink.

The names are direction-neutral on purpose (previous, not left), so a role stays correct if a layout is ever mirrored. Right-to-left layouts are not supported in 2.0.

A component slot beats the role. Slots live in the options interface of the component, so a typo is a type error.

NameTypeDefaultDescription
dialog, drawercloseIcon — Close button.
alert, toastcloseIcon — Close button.
chip, fileUploadremoveIcon — Remove button.
select, combobox, datePicker, timePickerchevronIcon, clearIcon — Options toggle and clear button.
colorInputchevronIcon — Options toggle.
calendar, calendarRange, carousel, tabspreviousIcon, nextIcon — Step controls.
paginationfirstIcon, previousIcon, nextIcon, lastIcon — Step controls.
accordion, treedisclosureIcon — Expand control.
breadcrumbsseparatorIcon — Separator.
linkexternalIcon — Mark after an external link.
  • The chips inside a multiple Select read defaults.chip.removeIcon, then defaults.icons.remove.
  • Glyphs that are specific to one component and have no role (calendar, clock, search and zoom marks, play and pause, copy, file and folder, upload) are drawn by the same renderer, so they follow the stroke tokens, but they cannot be replaced yet.
  • Two glyph sets are outside this system: the plus, minus and chevron steppers of kuiNumberInput and the silhouette placeholder of kui-avatar. Charts draw data, not icons.

An icon is plain data in the icon-node layout of Lucide, so an icon from the Lucide packages can be used as is.

glyph.ts
interface KuiIconGlyph {
  readonly node: readonly (readonly [
    tag: string,
    attributes: Record<string, string | number | undefined>,
  ])[];
  readonly viewBox?: string; // default '0 0 24 24'
}
NameTypeDefaultDescription
Elementspath, line, polyline, polygon, circle, ellipse, rect — Any other element invalidates the glyph.
Attributesgeometry and paint — Geometry (d, cx, cy, r, rx, ry, x, y, x1, y1, x2, y2, width, height, points) and paint (fill, fill-opacity, fill-rule, clip-rule, stroke, stroke-opacity, stroke-linecap, stroke-linejoin, stroke-miterlimit, stroke-dasharray, stroke-dashoffset, opacity, transform). Everything else is ignored.
Valuesstrings and numbers — Values containing url(, <, > or javascript: are ignored.
Line weightstroke-width, vector-effect — Not accepted; the stroke tokens own them.
Filled shapesfill and stroke none — Set fill and stroke: none on the element, as the built-in play glyph does.
Invalid overrideignored — The next level of the precedence chain is used. In development mode the first use of an invalid glyph logs a warning.

Hand-drawn icons and other sets work the same way: draw them with strokes on a 24 by 24 grid, or set viewBox. Colour always follows currentColor, so a glyph picks up the surrounding text colour, hover and disabled states and the forced-colors palette.

Two public CSS custom properties control the line of structural icons and of stroke-based kui-icon content. Set them on the root or on any subtree.

NameTypeDefaultDescription
--kui-icon-stroke-widtha number in glyph-grid units — Built-in glyphs default to the weight of their call site (between 1.5 and 2.5); content icons keep the weight of their own markup until the token is set.
--kui-icon-vector-effectnone | non-scaling-stroke — non-scaling-stroke keeps the stroke the same number of pixels at any icon size. The default is none.
:root {
  --kui-icon-stroke-width: 1.5; /* thinner icons everywhere */
}

kui-icon exposes both through strokeWidth and absoluteStrokeWidth; they only set the tokens on the host, so an ancestor can set them too. Only stroke-based drawings respond: filled shapes, gradients and markup with an inline style stroke width do not. A scaling stroke is the default because a constant pixel stroke makes small icons blurry on some screens.

Structural icons are decorative plain template output.

  • Structural icons are decorative: aria-hidden="true" and focusable="false". The accessible name belongs to the owning control (the aria-label of the close button, the visible text of the chip) and an override never changes it.
  • Glyphs use currentColor, so they are visible in forced-colors mode with the system text colour. Icons drawn with a CSS mask-image disappear in Chromium in that mode, which is why this renderer draws real SVG.
  • A glyph is drawn through a fixed list of elements and attributes, never through innerHTML, and the Angular sanitizer bypass is not involved, so a glyph can come from any source. Icons that kui-icon fetches by name are converted to the same data with the same rules before they are drawn.
  • The server HTML already contains the icon and hydration reuses it. The generated remove button of a chip and the Color Input toggle are built in the browser only, as before, and follow the same defaults.

Use a subtree and provideKuiDefaults for per-instance needs.

  • Per-instance icon inputs on components.
  • Icon fonts, sprite sheets and CSS mask-image icons.
  • <g>, gradients, <use>, <image>, filters and text inside a glyph.
  • Render functions or component templates as an override value.