Skip to content
menumenu tanreiexpand_morecontrasttranslate
tanreiexpand_more
Getting startedWebReactSwiftUIJetpack ComposeFlutterReact NativePresetsYour own design systemUsing Tanrei with AI agentsBrandDesign tokensDesign systems and customizationTheme StudioColorTypographyIconsShapeElevationMotionInteraction statesBehaviourLayoutAccessibilityWordsColor tokensTypescale tokensShape tokensElevation tokensMotion tokensState tokensSpace and measure tokensComponentsButtonMain action buttonSegmented buttonButton groupButton with menuToolbarCopy buttonCheckboxRadio buttonSwitchChipMenuSliderSlider fieldCalendarDateTimePickerField groupRadio groupCheckbox groupSelectSelect fieldMenubarFile uploadFile picker AdvancedDrop zoneText input AdvancedText fieldChip fieldFieldCombobox AdvancedCombobox fieldNumber input AdvancedNumber fieldColor input AdvancedColor fieldOne-time-code input AdvancedOne-time-code fieldEmbedded text input AdvancedChat composerCardDividerModal layer AdvancedDialogListList rowBottom sheetSide sheetCarouselCode blockTablePopoverStepsAccordionDescription listFigureApp shellPage headerTree viewTimelineImage previewHandleResizable frameScroll areaBadgeProgress indicatorMeterNumber tickerLoading indicatorSnackbarTooltipKeyboard shortcutTagInline codeCalloutAvatarEmpty stateTag groupSkeletonFile itemSwatchMarquee textTabsTab viewApp barNavigation barNavigation drawerSearchLinkPaginationPrevious and next linksStepperBreadcrumbsTable of contentsLink cardFAB menuStatusHow Tanrei is builtContributingLicense and attribution
HomeFoundationsIcons

Icons

One icon set per design system, one weight, and one rule — outlined when unselected, filled when selected.

content_copyView as Markdown

Tanrei’s default icons are Material Symbols, Rounded, at weight 300. Like the text faces, the font is referenced by name and not bundled: a product loads it.

How an icon is drawn is a small group of tokens. The icon box comes in three sizes, sys.icon.small, medium and large (16, 20 and 24px by default), and the font’s optical size follows the box.

ref.typeface.icon
The icon font. Material Symbols Rounded: a variable font whose weight and fill are set from sys.icon.
sys.icon.family
The icon font by selection. A variable icon font (Material Symbols) is one family and tells selected from unselected with its FILL axis; a static icon font tells them apart with two families (Material Icons Outlined / Material Icons). Both default to ref.typeface.icon.
sys.icon.weight
The stroke weight of every icon (the `wght` axis of the icon font).
sys.icon.fill
The fill of an icon by selection (the `FILL` axis of the icon font): 0 is outlined, 1 is filled.

Generated from tokens/ref/typeface.tokens.json, tokens/sys/measure.tokens.json

The fill rule

Unselected is outlined. Selected is filled. Always. A navigation item, a tab, a toggle, a chip, a favourite: the icon stays the same glyph and only its fill changes with the state. Filled and outlined icons are never mixed for decoration, and never chosen by hand — on the web the fill follows the state of the control the icon sits in.

Fill follows selection

Add
import '@tanrei/web/icons.css';
<button aria-pressed="true"><span class="tanrei-icon" aria-hidden="true">favorite</span> Favourite</button>

.tanrei-icon reads aria-pressed, aria-selected, aria-checked and aria-current on any ancestor (on a Tanrei control, which moves them to data-aria-*, it reads data-aria-pressed, data-aria-selected, data-aria-checked and data-aria-current), or data-selected on the icon or an ancestor (data-selected="false" opts one icon out). Give an icon aria-hidden="true" when text next to it says the same thing, and the control an accessible name when it does not.

The name can also be given in data-icon, with the element left empty:

<span class="tanrei-icon" data-icon="folder"></span>

The glyph is the same, but its name is no longer text on the page: it is not in textContent or innerText, not in what people copy, and not in the accessible name of the control around it. Use it wherever the name would otherwise be read as text, such as a row in a menu or a list, a table cell, or text a test compares. Size, selection and right-to-left mirroring work exactly as they do for the text form. Keep aria-hidden="true" where the text beside the icon says the same thing: Firefox before 128 still reads the name into the accessible name.

On the native platforms the same values arrive with the design system (system.icon), together with a small helper that turns selected and a size into what the font needs. Tanrei does not ship an icon component yet.

An icon set per design system

The icon set belongs to the design system, so a layer can change it. What the shipped systems resolve to:

Design system family.unselected family.selected weight fill.unselected fill.selected
tanrei Material Symbols Rounded Material Symbols Rounded 300 0 1
cupertino Material Symbols Rounded Material Symbols Rounded 300 0 1
m3-expressive-like Material Symbols Outlined Material Symbols Outlined 400 0 1
m3-like Material Symbols Outlined Material Symbols Outlined 400 0 1

Generated from tokens/systems/*.system.json, design/tokens/<system>/tanrei.light.tokens.json

An icon font tells a selected icon from an unselected one in one of two ways, and the tokens cover both. A selected icon always takes family.selected and fill.selected; which of the two actually changes what is drawn depends on the set.

One variable family

A variable icon font, such as Material Symbols, is a single family with a FILL axis. Both family tokens point at ref.typeface.icon by default, so re-pointing that one token changes the set, and fill.unselected / fill.selected tell the states apart. This is all of what m3-like writes:

"tokens": {
  "tanrei.ref.typeface.icon": { "$value": ["Material Symbols Outlined"], "reason": "Material Design 3 draws Material Symbols in the Outlined style." },
  "tanrei.sys.icon.weight":   { "$value": 400, "reason": "Material Symbols default weight in Material Design 3." }
}

Two static families

A static icon font has no axes. It ships the outlined and the filled drawings as two separate families, so the layer names one for each state:

"tokens": {
  "tanrei.sys.icon.family.unselected": { "$value": ["Material Icons Outlined"], "reason": "Acme uses the static Material Icons set: outlined when unselected." },
  "tanrei.sys.icon.family.selected":   { "$value": ["Material Icons"], "reason": "Acme uses the static Material Icons set: filled when selected." },
  "tanrei.sys.icon.weight":            { "$value": 400, "reason": "A static icon font has one weight; 400 is what Material Icons is drawn at." }
}

The fill and weight tokens stay as they are. A static font ignores the axes, so they do no harm, and a component does not need to know which kind of set is active. This layer is the repository’s own test fixture (packages/tokens-build/test/fixtures/acme.system.json).

Icon names are the product’s concern

Tanrei decides how an icon is drawn, not which icons exist. Sets name their glyphs differently, and a set may lack a glyph another one has. A product that changes its icon set checks the names it uses. A symbol set that is not a font, such as SF Symbols, cannot be named by a family token; that is filed in the mimicry-gaps registry as platform-symbol-sets.

React Native

React Native’s <Text> cannot set font-variation axes, so with a variable icon font FILL never reaches the glyph and an icon looks the same in both states. To keep the rule there, use two static families as above, or draw the icons as SVG.

The icon helpers of the four native packages are being moved to the family-per-state model now; until a package’s README describes it, expect that package to read a single family.