Components
Button
A pill button, filled with its colour by default. Glass and outline are the quieter weights for secondary actions.
Preview
Variants
Pick a variant with the controls. The preview and both code blocks follow.
Variant
Tone
Size
Icon
Disabled
Variant.tsx
<AppButton glyph="compass">Explore</AppButton>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
<AppButton glyph="compass" tone="deep-space">Explore</AppButton><AppButton tone="sodium-airglow" variant="outline">Details</AppButton>app-icons.tsx · AppButton
/** Button. Filled is the default; glass and outline are quieter. */export function AppButton({ glyph, tone = "deep-space", variant = "filled", size, disabled, children,}: { glyph?: GlyphName; tone?: TileTone; variant?: ButtonVariant; size?: "sm"; disabled?: boolean; children: string;}) { const cls = ["app-ui app-button", variant !== "filled" && `is-${variant}`, size && `is-${size}`].filter(Boolean).join(" "); return ( <button type="button" className={cls} style={{ ["--tone" as string]: toneVar(tone) }} disabled={disabled}> {glyph && <Glyph name={glyph} />} {children} </button> );}app-ui.css
.app-button { --tone: var(--color-deep-space-200); position: relative; display: inline-flex; align-items: center; justify-content: center; gap: var(--spacing-f8); height: var(--spacing-f55); padding: 0 var(--spacing-f21); border-radius: var(--radius-full); border: var(--spacing-f1) solid color-mix(in srgb, var(--c-text) var(--mag-3), var(--tone)); background: linear-gradient(180deg, color-mix(in srgb, var(--c-text) var(--mag-3), var(--tone)), var(--tone)); color: var(--c-ink); font-family: inherit; font-size: var(--text-s0); line-height: 1; font-weight: 700; cursor: pointer; box-shadow: var(--highlight), 0 var(--spacing-f8) var(--spacing-f21) calc(var(--spacing-f8) * -1) var(--tone); transition: transform 0.4s cubic-bezier(0.215, 0.61, 0.355, 1), box-shadow 0.4s;}.app-button svg { width: var(--spacing-f21); height: var(--spacing-f21); fill: currentColor; }.app-button:hover { transform: translateY(calc(var(--spacing-f2) * -1)); box-shadow: var(--highlight), 0 var(--spacing-f13) var(--spacing-f21) calc(var(--spacing-f5) * -1) var(--tone); }.app-button.is-glass { background: linear-gradient(180deg, color-mix(in srgb, var(--tone) var(--mag-3), var(--c-ink)), color-mix(in srgb, var(--tone) var(--mag-5), var(--c-ink))); border-color: color-mix(in srgb, var(--tone) var(--mag-2), transparent); color: var(--c-text); font-weight: 500;}.app-button.is-outline { background: transparent; border-color: var(--tone); color: var(--tone); box-shadow: none; font-weight: 500; }.app-button.is-sm { height: var(--spacing-f34); padding: 0 var(--spacing-f13); font-size: var(--text-s-1); }Usage
AstrGlassButton(label: 'Explore',icon: Icons.explore,tone: AstrTone.deepSpace,variant: AstrButtonVariant.filled,onPressed: () {},)astr_glass.dart · AstrButtonVariant
/// Visual weight of an [AstrGlassButton]. Filled is the default.enum AstrButtonVariant { filled, glass, outline }astr_glass.dart · AstrGlassButton
/// A pill button, [AstrSize.control] tall (55, the touch target), or/// [AstrSize.controlCompact] (34). Filled uses the tone as its fill with dark/// text; glass tints the surface; outline draws only the edge. Pressed, it/// squeezes slightly and glows in its tone, even the quiet variants.class AstrGlassButton extends StatefulWidget { const AstrGlassButton({ super.key, required this.label, this.icon, this.tone = AstrTone.deepSpace, this.variant = AstrButtonVariant.filled, this.compact = false, this.onPressed, }); final String label; final IconData? icon; final AstrTone tone; final AstrButtonVariant variant; final bool compact; final VoidCallback? onPressed; @override State<AstrGlassButton> createState() => _AstrGlassButtonState();}API reference
| React prop | Flutter parameter | Type | Default | Meaning |
|---|---|---|---|---|
| children | label | string | required | The text. |
| glyph | icon | GlyphName / IconData | none | Leading glyph. |
| tone | tone | AstrTone | deep-space | Accent colour. |
| variant | variant | filled, glass, outline | filled | Visual weight. |
| size | compact | sm / boolean | 55 tall | 55 is the touch target; sm is 34. |
| 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 |
|---|---|---|---|
| Height | --spacing-f55 (touch target), --spacing-f34 compact | AstrSize.control, AstrSize.controlCompact | Explore button 27 |
| Shape | --radius-full | BorderRadius.circular(…) pill | kExploreButtonRadius 8 |
| Padding | 0 --spacing-f21, compact f13 | AstrSpace.f21, f13 | none |
| Type | --text-s0 w700, compact --text-s-1 | AstrType.s0, sNeg1 | labelMd 12 w500 |
| Filled fill | tone, lightened by --color-space-grey-50 at --mag-3 | Color.alphaBlend(text, Mag.m3) | AppColors.accent #448AFF |
| Text on fill | --color-space-grey-950 | _ink | textPrimary on accent |
| Glyph | --spacing-f21 | AstrSize.icon | none |
| Disabled | opacity --mag-2 | Mag.m2 | none |
| Pressed | scale 0.97, glow like hover (quiet variants glow while held) | AnimatedScale 0.97, BoxShadow | none |