TerraUI v0.1.1

Material

TerraUI's controls are molded plastic lit from above. Raised keys travel onto their edge, surfaces are shells, and wells are cut into them.

Every TerraUI component is cut from one material: molded plastic under a single light above the screen. Three treatments come from it. A key is raised and acts, a shell holds content, and a well is cut into a shell. Each one is box shadows and a gradient, so it looks the same on iOS and Android and never animates on its own.

Keys

A key is every raised control face: Button, IconButton, Shutter, the Switch key and the tab bar's central action.

  • Face over edge: a softly domed face sits on a visible lower edge in a darker tone of its colour (accentEdge under accent). The edge is depth.travel deep, 4 pt by default, or depth.travelSm, 3 pt, on a small key.
  • Light: a rim light runs along the top (depth.highlight), a sheen falls down the face (depth.sheen) and a shade sits where the face turns under (depth.shade). Round faces, the Shutter and the tab bar's action, catch the light in a spot near the top instead.
  • Seated on the shell: a short contact shadow under the edge (depth.contact) and a soft halo on the shell around its base (depth.halo).

Press

Pressing travels the face down onto its edge in motion.pressIn (70 ms) and lays colors.pressTint over it. Releasing relaxes it back in motion.pressOut (160 ms). The press is the feedback: no ripple, no scale and no glow.

A key whose face is smaller than the platform's touch minimum, 44 pt on iOS and 48 dp on Android, keeps its face and grows its touch area around it.

Flat faces

A ghost Button has no edge: it is a matte face that tints when pressed and never travels. Use it for actions that should read as links.

Shells

A shell is a content surface resting on the page: Card, Notice, Stat, Task, a raised Cell, the tab bar deck and the header bar.

  • Lift: a shallow shadow seats it on the page (depth.surface).
  • Rims: a 1 pt rim light inside the top edge, a sheen from lit to clear across the top, and a 1 pt turned-under line inside the bottom edge (depth.shell.rim).
  • Light and dark: in light, the lower half also falls into a faint shade (depth.shell.shade), because a white shell cannot get lighter. In dark, the rim light carries it alone.
  • Stacking: a shell resting on another shell (a Notice in a Card, a Cell in a panel) takes the shallower depth.surfaceNested lift, so the two read as one molded part. Keys keep their full edge and halo wherever they sit.
  • Bars: the header bar is a shell hung from the top, casting its lift down. The tab bar deck casts its lift upward onto the page.

Wells

A well is the inverse of a key: a recess cut into the shell. It holds the Progress track, an empty Cell, an Input, a selected tab and a disabled Button.

  • Lips: the upper lip casts a shadow down into the recess (depth.recess) and the lower lip catches a line of light (depth.lip).
  • Floor: colors.surfaceMuted. In dark it sits below surface, never beside it, so the recess reads as lower.
  • Inputs: an Input is a well sunk half as deep. It shows no stroke at rest; focus and error draw a 2 pt stroke.

Material helpers

For a surface of your own, take the material from the theme instead of drawing shadows by hand. Each helper takes the theme from useTheme() and returns style to spread on a View:

  • recessedWell(theme, { softLip, depth }): a well's lips as inset shadows. softLip keeps a quarter of the lower lip's light, for a well on a coloured or living ground; depth scales how far it sinks (1 by default).
  • wellSurface(theme): a full well, the surfaceMuted floor plus recessedWell. The shape and padding stay yours.
  • discLift(theme): the contact shadow of a key, for a small object resting on a surface, such as an avatar disc.
  • surfaceLift(theme): a shell's lift, for a floating panel such as a callout.
const theme = useTheme();
<View style={[{ borderRadius: theme.shape.radiusMd, padding: theme.spacing.md }, wellSurface(theme)]} />

States

One grammar covers every control:

  • Pressed: travel plus tint, as above.
  • Selected: a 2 pt accent border, the soft accent fill, and a marker and bolder label in onAccentSoft. Selection reads through shape and label, never through colour alone.
  • Disabled reads recessed: a disabled key sinks into the shell as a well, with the surfaceMuted floor, no edge and no action. Raised means it acts and recessed means it does not, so a grey secondary key and a disabled one never look alike. Its label stays readable (onDisabled).
  • Loading: the key keeps its size, shows a Spinner in its label's colour over its hidden label, stops acting and reports itself busy.
  • Error: shown next to the control that caused it, without discarding what the user entered.

Reduce Motion

TerraUI follows the system Reduce Motion setting live, through useReducedMotion. When it is on:

  • keys no longer travel, but keep their edge and their press tint, so the pressed state stays legible;
  • springs jump to their end, such as a Progress fill or a Switch key;
  • decorative motion stops, such as the busy wave along a Switch's grip bars.

Your own animated components can read the same setting with useReducedMotion().

Rules

  • Let the material carry depth. Don't add borders, glows or extra shadows to TerraUI surfaces.
  • Keep physical depth on controls. Content surfaces stay shallow.
  • Keep coloured glows off chrome: shells and keys cast neutral shadows.
  • Never fake a key with a View and a shadow. Use a Button, which brings the travel, the tint, the touch target and the accessibility state.
  • Tune the material through depth in your theme, not with styles on each component. See Theming.

On this page