On this page
Usage
import { Button, Column, Surface, Table, Text, TextField, Theme } from '@envsemble/envui';
// Padding is inside the surface; gap is between its children.
<Surface padding={5} render={<Column gap={4} />}>
<Text render={<h2 />} variant="title">
Workspace
</Text>
<Text>Shared plans and notes for the project.</Text>
</Surface>;
// A desktop productivity app: one compact scale for the whole window, and tighter rows.
<Theme theme="light" scale="compact">
<TextField label="Title" defaultValue="Quarterly report" />
<Button>Rename</Button>
<Table.Root aria-label="Reports" density="sm">
…
</Table.Root>
</Theme>;
| Prop | Takes |
|---|---|
gap, padding | Spacing steps 0–9 on Row, Column, Grid, Box and Surface; 0 to 4 rem in the comfortable scale, smaller in compact |
| Theme scale | system, comfortable or compact; inherited from the enclosing Theme, system outside any; written to data-env-scale |
| ThemePreferenceProvider scale | As on Theme, for its boundary and the document root; left out, it inherits <html data-env-scale> |
| createThemeBootstrapScript | scale: 'compact' or 'comfortable' writes data-env-scale to the root before the first paint; system writes nothing |
Control size | sm, md or lg on Button, IconButton, the fields, Select, Combobox, DatePicker, SegmentedControl, Pagination and the rail of Tabs |
Row density | xs, sm, md or lg on Table.Root, Tree and List.Root: only the rows' block padding changes |
| Touch minimum | 2.75 rem: every control grows to it under a coarse pointer |
The words of the figures
- Tight, inside a group:
gap1–3: a heading and its description, a label and its value, short lines that read as one block. - Roomier, between groups:
gap4–5: the fields of a form, a form and its actions, the groups of a section. - The most, between sections:
gap6–9: the sections of a page or a settings pane. - Components bring their own padding: don't pad a Button, field or Card from outside; arrange the space between them.
- Big enough for a finger: nothing to do; controls grow to the touch minimum under a coarse pointer, as Touch says.
Composition
Two axes of density
- Scale is the size of the whole system,
comfortableorcompact: it switches every size token at once. Compact is about 0.8 of comfortable in space and corners, with controls a step shorter and type a step smaller, for desktop productivity apps. Icons keep a 1 rem floor; borders and focus rings keep their widths.system, the default, is compact underpointer: fineand comfortable otherwise; the stylesheet decides, so the server render and the first paint already have it. - Row density is how tightly Table, Tree and List pack their rows:
xstolg, padding only.
Within a scale, size picks one of three control heights for a deliberate local difference; every control of one size lines up. A control without a size takes its container's --env-control-height, md unless the container sets another, as a table cell does with sm.
Guidelines
- The parent owns gaps, through Row, Column and Grid; each component owns its padding. Text has no outer margins.
- Use relative units; let text wrap and controls grow with their content. In your own grids,
minmax(0, 1fr)tracks,min-inline-size: 0on flex children andmax-inline-size: 100%on media. - Choose Surface for a plain pane and Card for a content surface, and keep their corners.
- Use
--env-radius-fullwhere a pill is meant, never50%or9999px: one line tall it draws round ends, and an element that grows taller, such as a wrapping button, keeps the same corners.50%is only for parts that are always square, such as avatars and round icon buttons. - One scale per app or window, on the root Theme or ThemePreferenceProvider. Leave it at
systemfor a tool used with a mouse and on touch; setcomfortablefor a website with room to breathe, orcompactfor a dense tool on touch too. A fixed scale, rendered on the server, goes to the provider and tocreateThemeBootstrapScripttoo, so the first paint has it. - Another scale only at a Theme boundary around a whole region, such as a docked inspector:
<Theme {...useTheme()} scale="compact">. - Rows match: a button beside a field takes its size, and a table's toolbar follows its rows.
- Choose a row density for how people read:
smorxsfor scanning,mdorlgfor reading row by row. - Read the tokens in application CSS, such as
block-size: var(--env-control-height-sm).
Customize
Inside a Theme these tokens follow the theme, its scale and the reader's text size. Their values in each scale are under Sizes in each scale, and every value is on the Design tokens page of the Docs.
| Token | Holds |
|---|---|
--env-space-1 … --env-space-9 | The spacing scale |
--env-radius-xs | 0.25 rem: small marks, checkboxes and color swatches |
--env-radius-sm | Inner rows, important actions and thumbnails |
--env-radius-lg | Alerts, toasts, fields, text areas, tables and drop zones; a single-line field's corner is no more than a third of its height |
--env-radius-surface | Panes, cards, dialogs, sheets and the outside of a row group |
--env-radius-full | Rounded ends: buttons, navigation items, badges; half the tallest single-line control, 1.5 rem at most, so a taller element is a rounded rectangle |
--env-control-height, --env-control-height-sm / -md / -lg | Control heights, to line up with |
--env-control-icon-size | 1 rem: the icon inside controls |
Corners by what they are
The catalogue shows four kinds of corner; small marks are a fifth, for code. Their tokens:
| In the catalogue | Token and where it is used |
|---|---|
| Surfaces | --env-radius-surface: panes, cards, dialogs, sheets and the outside of a row group; popups take a smaller corner, concentric with their rows |
| Inside a surface | --env-radius-lg for alerts, toasts, fields, tables, text areas and drop zones; --env-radius-sm for list rows, menu items, tabs and tooltips |
| Fully rounded | --env-radius-full: buttons, badges, switches, segmented controls and navigation items |
| Circles | 50%: avatars and icon buttons, which are always square |
| Small marks | --env-radius-xs: checkboxes and color swatches, which read as boxes |
Sizes in each scale
At the default 16 px root; every value is a token in rem.
| Token | Comfortable | Compact |
|---|---|---|
--env-control-height-sm / -md / -lg | 32 / 40 / 48 | 28 / 36 / 44 |
--env-row-height (calendar days; popup rows are 0.25 rem more) | 36 | 28 |
--env-row-padding-block-xs / -sm / -md / -lg | 2 / 4 / 8 / 12 | 0 / 2 / 4 / 6 |
--env-space-1 … --env-space-9 | 4, 8, 12, 16, 24 … 64 | 4, 6, 10, 12, 20 … 52 |
--env-font-size-caption / -body-sm / -label | 12 / 14 / 14 | 11 / 12 / 13 |
--env-font-size-body / -title / -headline | 15 / 18 / 22 | 13 / 16 / 20 |
--env-icon-size-sm / -md / -lg | 16 / 18 / 20 | 16 / 16 / 18 |
--env-picture-size-sm / -md / -lg / -xl (Avatar, Graphic) | 24 / 32 / 48 / 88 | 24 / 32 / 48 / 88 |
--env-radius-sm / --env-radius-lg / --env-radius-surface | 8 / 16 / 28 | 6 / 12 / 22 |
--env-top-bar-height / --env-title-bar-height | 52 / 40 | 42 / 32 |
--env-control-target-min / --env-control-height-touch | 24 / 44 | 24 / 44 |
Touch
- Under a coarse pointer,
@media (pointer: coarse), controls and rows grow to at least 44 px in either scale, and entered text becomes 1 rem so phones don't zoom into a field. A touchscreen laptop used with a mouse keeps the desktop density. - A control's own box is its hit area, so a press always reaches the control it lands on.
- Grow your own targets the same way, since
--env-control-heightkeeps its value under touch:
.my-target {
block-size: var(--env-control-height);
}
@media (pointer: coarse) {
.my-target {
block-size: max(var(--env-control-height), var(--env-control-height-touch));
}
}
- A link in text, a breadcrumb and a Switch's track stay smaller, under WCAG 2.5.8's spacing exception; with a mouse every target is at least 24 px.
TypeScript declarations provide the complete public API.
Words in code
| Words | In code |
|---|---|
| More room between these | The next larger step of the gap that parts them, on their parent. |
| Group these closer | Wrap them in their own Column or Row with a smaller gap, inside the larger one. |
| Separate sections | A larger gap between them, never a line. |
| Line these up | One size for every control in the row, and align="center" on the Row. |
| A small button | size="sm", "md" (the default) or "lg" on a control. |
| Compact | scale="compact" on the root Theme: every control height, gap, type size and corner a step smaller. |
| Comfortable | scale="comfortable" on the root Theme. |
| Follow the device | scale="system", the default: compact under a fine pointer, comfortable on touch. |
| Tighter rows | density="sm" or "xs" on a Table, List or Tree; the text keeps its size. |
| On a surface | Surface for a plain pane, Card for a content surface; both keep --env-radius-surface. |
| Fully rounded | --env-radius-full, as Composition says. |