Components
Icon tile
A tinted glass square that holds one glyph and fills with its colour when selected. Use it for icon-only actions where a label would crowd the screen.
Preview
Variants
Pick a variant with the controls. The preview and both code blocks follow.
Glyph
Tone
Size
State
Variant.tsx
<AppIconTile glyph="moon" label="Moon" />Implementation
The React tab is this site's own component, the same source that draws the preview, styled by the CSS below it. The Flutter tab is the Dart a screen builds it with, from the app's tokens.
Usage
<AppIconTile glyph="moon" tone="deep-space" label="Moon" /><AppIconTile glyph="graph" tone="aurora-green" label="Graphs" pressed />app-icons.tsx · AppIconTile
/** Glass icon tile. Pass `pressed` for a toggle; `label` is the accessible name. */export function AppIconTile({ glyph, tone = "deep-space", size, label, pressed, disabled,}: { glyph: GlyphName; tone?: TileTone; size?: "sm" | "lg"; label: string; pressed?: boolean; disabled?: boolean;}) { return ( <button type="button" className={`app-ui app-tile${size ? ` is-${size}` : ""}`} style={{ ["--tone" as string]: toneVar(tone) }} aria-label={label} aria-pressed={pressed} disabled={disabled} > <Glyph name={glyph} /> </button> );}app-icons.tsx · Glyph
export const Glyph = ({ name }: { name: GlyphName }) => ( <svg viewBox="0 0 24 24" aria-hidden="true"> <path d={GLYPHS[name]} fillRule="evenodd" /> </svg>);app-ui.css
.app-tile { --tone: var(--color-deep-space-200); --tile-size: var(--spacing-f55); position: relative; display: inline-grid; place-items: center; width: var(--tile-size); aspect-ratio: 1; border-radius: calc(var(--tile-size) * var(--phi-inverse-squared)); border: var(--spacing-f1) solid color-mix(in srgb, var(--tone) var(--mag-3), transparent); color: var(--tone); cursor: pointer; overflow: hidden; isolation: isolate; background: radial-gradient(circle at 50% 115%, color-mix(in srgb, var(--tone) var(--mag-1), transparent), transparent 65%), linear-gradient(160deg, color-mix(in srgb, var(--tone) var(--mag-4), var(--c-ink)), color-mix(in srgb, var(--tone) var(--mag-6), var(--c-ink))); box-shadow: var(--highlight), 0 var(--spacing-f13) var(--spacing-f34) calc(var(--spacing-f13) * -1) color-mix(in srgb, var(--tone) var(--mag-1), transparent); transition: transform 0.4s cubic-bezier(0.215, 0.61, 0.355, 1), box-shadow 0.4s;}.app-tile svg { width: calc(var(--tile-size) * var(--phi-inverse-squared)); height: calc(var(--tile-size) * var(--phi-inverse-squared)); fill: currentColor; filter: drop-shadow(0 0 var(--spacing-f8) color-mix(in srgb, var(--tone) var(--mag-1), transparent)); }.app-tile:hover { transform: translateY(calc(var(--spacing-f3) * -1)); }.app-tile[aria-pressed="true"] { background: var(--tone); color: var(--c-ink); box-shadow: var(--highlight), 0 var(--spacing-f13) var(--spacing-f34) calc(var(--spacing-f8) * -1) var(--tone); }.app-tile.is-sm { --tile-size: var(--spacing-f34); }.app-tile.is-lg { --tile-size: var(--spacing-f89); }Usage
AstrGlassTile(icon: Icons.nightlight_round,label: 'Moon',tone: AstrTone.deepSpace,size: AstrTileSize.md, // 55selected: false,onPressed: () {},)astr_glass.dart · AstrTone
/// The five accent tones for glass tiles and buttons, named by the colour they/// are. Each is a stop from [AstrColors], so a tone can never drift from the/// colour system.enum AstrTone { deepSpace, auroraGreen, auroraPink, sodiumAirglow, oxygenAirglow; Color get color => switch (this) { AstrTone.deepSpace => AstrColors.deepSpace[200]!, AstrTone.auroraGreen => AstrColors.auroraGreen[400]!, AstrTone.auroraPink => AstrColors.auroraPink[400]!, AstrTone.sodiumAirglow => AstrColors.sodiumAirglow[400]!, AstrTone.oxygenAirglow => AstrColors.oxygenAirglow[400]!, };}astr_glass.dart · AstrTileSize
/// Tile edge lengths: the Fibonacci steps 34, 55 and 89. The corner radius is/// the golden minor part of the edge (0.382), which lands on the next/// Fibonacci radius down: 13, 21 and 34.enum AstrTileSize { sm(AstrSpace.f34), md(AstrSpace.f55), lg(AstrSpace.f89); const AstrTileSize(this.edge); final double edge;}astr_glass.dart · AstrGlassTile
/// A tinted glass square holding one glyph, with a bloom of its tone along the/// bottom edge. When [selected] the tile fills with the tone. Pressed, it/// squeezes slightly and its glow grows, like the hover state on the web.class AstrGlassTile extends StatefulWidget { const AstrGlassTile({ super.key, required this.icon, required this.label, this.tone = AstrTone.deepSpace, this.size = AstrTileSize.md, this.selected = false, this.onPressed, }); final IconData icon; /// Accessible name, read by screen readers. final String label; final AstrTone tone; final AstrTileSize size; final bool selected; final VoidCallback? onPressed; @override State<AstrGlassTile> createState() => _AstrGlassTileState();}API reference
| React prop | Flutter parameter | Type | Default | Meaning |
|---|---|---|---|---|
| glyph | icon | GlyphName / IconData | required | The shape drawn at 44% of the tile. |
| tone | tone | AstrTone | deep-space | Accent colour; a system stop. |
| size | size | sm, md, lg / AstrTileSize | md | 34, 55 or 89. |
| label | label | string | required | Accessible name. |
| pressed | selected | boolean | false | Filled state for toggles. |
| disabled | onPressed: null | boolean | false | Not interactive. |
Design tokens
Every value on this component is a system token. The last column is what the app widget uses today, so the gap to close is visible.
| Role | CSS token | Dart token | App widget today |
|---|---|---|---|
| Sizes | --spacing-f34, f55, f89 | AstrTileSize.sm, md, lg | new |
| Corner radius | size × --phi-inverse-squared (13, 21, 34) | size * AstrLayout.minor | new |
| Glyph size | size × --phi-inverse-squared | size * AstrLayout.minor | new |
| Border | tone at --mag-3 | tone.withValues(alpha: Mag.m3) | new |
| Fill | tone at --mag-4 to --mag-6 over --color-space-grey-950 | Color.alphaBlend(tone, Mag.m4 and m6) | new |
| Selected fill | the tone, glyph --color-space-grey-950 | tone, _ink | new |
| Disabled | opacity --mag-2 | Mag.m2 | new |
| Tones | --color-deep-space-200, aurora-green-400, aurora-pink-400, sodium-airglow-400, oxygen-airglow-400 | AstrTone.color | new |