alouette
Version:
A modern, customizable design system built on top of NativeWind v5 with configurable defaults
74 lines (61 loc) • 3.91 kB
Markdown
# Button materials — which variant and accent, on which ground
The rules are in SKILL.md; this is why each holds, and the cases at the edges.
## tonal, neutral and filled
`tonal` is a ground **lighter than the page it sits on**, lifted by
`shadow-s`: the lightest card step when neutral, the accent's pale tints in light
mode and its own dark ground in dark mode. The hue is carried by the
`text-on-tonal` ink (label and icon) — the accent itself on a light ground,
the sharp ink on a dark one — because a tint pale enough for dark ink cannot
carry it (a pale red reads as pink).
`accent="neutral"` resolves to the plain mode theme (alouette-theming/SKILL.md), so
the button keeps the tonal material — ground, shadow, hover/focus/press
states — on the grayscale palette: a white card with sharp ink. That is a lone
action that must not carry the accent: a `Menu` trigger, the Add of a field
array, a `Modal` footer's single "Close". Beside a primary action it becomes
`variant="soft"`: a neutral `tonal` twin reads as a second equal choice, and on
a white `bg-highlight` ground — a `Modal` or `AlertDialog` panel, the `AppHeader`
bar — its ground is the ground itself and dissolves. The form editor modals'
Cancel and the header's "Sign up" beside "Log in" are neutral `soft`.
`filled` is the accent's own fill, flat, under the `text-on-accent` label —
for the one action that must dominate its neighbours. The neutral theme is an
accent, not the absence of one: its fill is the sharp ink turned into a ground,
so a neutral `filled` button is black with a white label in light mode and
white with a dark label in dark mode. Beside a `filled` call to action, the
other action is neutral `soft`.
**On an accented surface** (`<Box accent className="surface">`, a
`GradientBackground`), a lone `tonal` button is always `accent="neutral"`: in light
mode an accented `tonal` ground is the accented surface's own step, so it
dissolves into it. The accented action there is `filled`, or `soft` for chrome.
What counts is the ground, not the theme: a `bg-highlight` panel stays neutral
inside an accented theme, so an accented `Modal` or `AlertDialog` keeps a
`tonal` confirm (with a neutral `soft` Cancel). On web, `PressableBox` warns
outside production when an accented `tonal` pressable has the same ground as the
surface behind it.
```tsx
<Box accent="danger" className="surface">
<Button accent="neutral" variant="soft" text="Keep" onPress={keep} />
<Button variant="filled" accent="danger" text="Delete" onPress={remove} />
</Box>
```
## `soft` beyond the secondary action, and `outlined`
```tsx
<Button variant="soft" text="Docs" />
<IconButton variant="soft" icon={<XRegularIcon />} aria-label="Close" />
```
`soft` has no ground and no border at rest: the affordance is a fill arriving on
hover/focus/press, and that fill is a tone of the surrounding surface rather than
the accent. A `PressableBox` row composed on it (a header pressable, a menu row)
therefore keeps its own label color — never flip it to `text-on-accent`. It is
what a header pressable or a menu row wants, where a border tint reads as noise,
and the icon-only affordance inside a component's own frame — the modal close,
the message dismiss, the remove button of a field-array row — where a second
ground inside the frame would read as a nested card. With no ground to carry the
accent, the soft `Button` carries it in its label (`text-accent`, underlined —
a text button), and the soft `IconButton` glyph turns `text-accent` on
hover/focus/press.
`outlined` has no default use. Reach for it only for a pressable that must not
carry a ground and is not chrome — a row of equal-weight actions in a dense
toolbar. A secondary action is `accent="neutral" variant="soft"`, not
`outlined`.
Source: packages/alouette/src/ui/actions/PressableBox.tsx; ui/actions/Button.tsx;
ui/actions/IconButton.tsx