Docs menu

Design system

ADL is ANVL's design language. Every widget and element draws with its tokens, so apps look like one family and a theme change recolors everything at once.

The theme

ui.theme() is the current Theme; ui.set_theme(theme) replaces it from the next frame. Theme::adl_dark() is the default: dark zinc. Make your own by changing fields:

let mut theme = Theme::adl_dark();
theme.name = "Ocean";
theme.primary = anvil_ui::hex(0x38bdf8);
ui.set_theme(theme);
FieldUse
bgThe canvas behind everything.
sunkenInput wells, which sit below the surface around them.
surfaceBars, headers and sidebars.
card, card_hiCards; hover fills, secondary buttons and selected rows.
border, ringLines; focus and selection outlines.
text, text_dim, mutedText; secondary text readable at body sizes; captions and hints (13 px and up only).
disabled, disabled_bgDisabled controls and placeholders.
primary, primary_fgThe one main action on a screen, and the text on it.
on_colorText on a status fill (Delete, Done).
success, warning, dangerStatus colors.
selection, codeSelected text; inline code.
radiussm 4, md 8, lg 12, pill 9999 (logical px).
glass_alphaHow opaque a glass surface's tint is over its blur (0.8).

In element trees, refer to colors by role (Tok::Card) and radii by name (.rounded_lg()). They resolve when painted, so a new theme needs no rebuild. Colors are [r, g, b, a] floats; hex(0xRRGGBB), with_alpha(c, a) and lerp(a, b, t) help.

A grid of twenty color swatches for the default theme, each labelled with its token name, from the background to the on-color white.
Every color role of Theme::adl_dark(). Each swatch is the token it names.

Layers

Three draw layers keep floating things above content whatever order you draw in: 0 is the base, 1 is floating (menus, popovers, sheets), 2 is top (toasts, tooltips, dropdowns). Shadows are for layers 1 and 2 only; content on layer 0 is separated by borders and fills.

A settings card in the default dark theme: a dark gray card with a Sync heading, a green On, body text, and a light Pair a device button beside a Later outline button.
The default theme
The same card in the Ocean theme: a navy background and card, with a bright sky-blue Pair a device button.
The Ocean theme: the same code with a new theme

Glass and blur

ui.glass(r, radius, tint) (or .glass() on an element) draws a blurred copy of what is behind, tinted at the theme's glass_alpha, with the overlay shadow. Use it on floating surfaces: it reads what layer 0 drew. ui.backdrop_blur(r, radius) blurs without the tint. ui.set_backdrop_blur(false) turns blur off app-wide (for low-end devices); glass then draws an opaque tint.

Motion

ADL's motion tokens, in seconds, in anvil_ui::motion:

TokenValueUse
INSTANT0.09Hover, press, color changes.
FAST0.16Tooltips, small fades, focus rings.
BASE0.24Toasts, menus, list items entering.
SCREEN0.28Screen transitions.
SLOW0.32Sheets and modals.
STAGGER0.04Delay between list items entering (at most STAGGER_MAX = 5).
TOOLTIP_DELAY0.5Hover before a tooltip shows.

The motion helpers on Ui. Each keeps its state by id, so call it every frame:

  • animate(id, channel, target, rate) -> value: eases toward target; motion::rate(BASE) converts a duration to a rate. The first call starts at the target, so nothing animates in from zero.
  • spring(id, channel, target, stiffness, damping): a damped spring. motion::SPRING (260/23) overshoots about 4%; SPRING_CALM (300/35) does not.
  • presence(id, visible, enter, exit) -> 0..1: drives something that appears and disappears; keep drawing it until the value reaches 0.
  • entrance(id, index) -> 0..1: a staggered first appearance.
  • shake(id) and shake_offset(id): the horizontal shake for a rejected field.
  • pulse(hz): a 0..1 wave for pulsing indicators.

Anything that moves asks for the next frame by itself, and frames stop when it settles. With set_reduced_motion(true) everything jumps to its end state.

Breakpoints and safe areas

ui.breakpoint() is Compact (under 640 logical px: one column, bottom navigation), Medium (640 to 1024: two columns) or Expanded (over 1024: split panes). ui.safe_area() is the screen minus the system bars and notch on phones; lay out inside it. Set the insets each frame with ui.set_insets(Insets { … }) from platform::soft_input::window_insets() on Android.