Layout and responsive

Layout tokens set the widths where Nuton's layouts change, the order in which surfaces stack, and the focus ring. Use them for page structure, overlays and keyboard focus; use spacing tokens for the space inside a layout.

  • Beta
  • Accessibility
  • Figma Code-only
  • packages/tokens/src/{code,interim}

Beta. Breakpoints, z-index and focus-ring width and offset are code-only by design (src/code): Figma variables can't express them. The focus-ring colour is interim (src/interim) and isn't applied to components until C5 is approved. Blocks Stable: C5 approval, the bottom-nav item set (D23), tablet and three-column collapse designs in Figma (D23 backlog), and a reflow check at 320px.

Overview

Three small sets of tokens. Breakpoints decide when a layout rearranges, the z scale decides which surface wins when two overlap, and the focus ring makes the keyboard position visible on every surface.

  • sm from 640px
  • md from 768px
  • lg from 1024px
  • xl from 1280px
  • 2xl from 1536px
BreakpointsMobile first: each one is a minimum width. Below sm is the base layout.
  1. tooltip 80
  2. toast 70
  3. popover 60
  4. modal 50
  5. overlay 40
  6. sticky 20
  7. raised 10
  8. base 0
LayersTop first. Pick a layer by role; the numbers leave room between roles.
Focus ringA 2px outline, 2px clear of the control. Shown statically; components still use Figma's focus effect (C5).

Tokens

Generated from @nuton/tokens. Code-only tokens live in Git only and never as Figma variables; a check fails if a name exists in both places.

Breakpoints

Breakpoint tokens
TokenValueSource
--nu-breakpoint-sm640pxCode-only
--nu-breakpoint-md768pxCode-only
--nu-breakpoint-lg1024pxCode-only
--nu-breakpoint-xl1280pxCode-only
--nu-breakpoint-2xl1536pxCode-only

The same values as Tailwind's defaults, which the teacher app and marketing site already use. Figma has desktop (1440) and iPhone (393) frames and no tablet frames (D23 backlog). CSS can't read a variable inside @media; see Usage.

Content widths

Content width tokens
TokenValueSource
--nu-measure-reading46remCode-only
--nu-measure-content52remCode-only
--nu-measure-page66remCode-only

The widths a screen's content keeps on a large window, so it is neither a phone stretched across a tablet nor a narrow column lost in the middle. measure-reading is about 70 characters for lessons and long text; measure-content is for a focused start screen or form; measure-page is for lists, grids and settings. A screen centres its content at one of these and lets columns follow the room inside it.

Layers (z-index)

Z-index tokens
TokenValueSource
--nu-z-base0Code-only
--nu-z-raised10Code-only
--nu-z-sticky20Code-only
--nu-z-overlay40Code-only
--nu-z-modal50Code-only
--nu-z-popover60Code-only
--nu-z-toast70Code-only
--nu-z-tooltip80Code-only

Popover sits above modal on purpose: a menu or combobox opened from inside a dialog must appear over it.

Where each layer is used, top first
LayerUse it for
--nu-z-tooltipAlways on top. No tooltip component yet.
--nu-z-toastNotifications and this site’s skip link. No toast component yet.
--nu-z-popoverMenus, listboxes and comboboxes (Menu.css), so a menu inside a dialog still opens above it.
--nu-z-modalModal dialogs: the CommandMenu dialog.
--nu-z-overlayScrims and drawers: the CommandMenu scrim, this site’s mobile sidebar.
--nu-z-stickySticky headers. This site’s header uses it.
--nu-z-raisedReserved for content lifted within a card or list. Not used yet.
--nu-z-baseNormal document flow.

Focus ring

Focus ring size tokens
TokenValueSource
--nu-focus-ring-width2pxCode-only
--nu-focus-ring-offset2pxCode-only
Focus ring colour token
PreviewTokenValueSource
--nu-focus-ring-color→ state-primary-base#3559e9Interim

Interim (C5): an alias of --nu-state-primary-base. Figma's focus effects (buttons/*-focus) measure about 1.1:1 against white; this colour measures 5.59:1. It's used by this site's own chrome, and moves to Figma once the DS Owner approves it.

Usage

Load @nuton/tokens/tokens.css once. Use the CSS variables for layers and focus, and the breakpoint values through Tailwind or JavaScript, because a media query can't read a CSS variable.

css
/* Layers: choose by role, never by number. */
.lesson-header { position: sticky; inset-block-start: 0; z-index: var(--nu-z-sticky); }
.tutor-sheet   { position: fixed; z-index: var(--nu-z-overlay); }

/* Focus: outline (not box-shadow) so it survives forced-colours mode. */
.lesson-card:focus-visible {
  outline: var(--nu-focus-ring-width) solid var(--nu-focus-ring-color);
  outline-offset: var(--nu-focus-ring-offset);
}

/* Breakpoints: var() doesn't work in @media. Copy the value from
   --nu-breakpoint-lg (1024px) and name the token beside it. */
@media (min-width: 1024px) { /* --nu-breakpoint-lg */
  .lesson { grid-template-columns: 16rem minmax(0, 1fr) 22rem; }
}
  • Write mobile first: the base styles are the narrow layout, and each breakpoint adds to it with min-width.
  • Components that live inside panels (the tutor, a sidebar) respond to their container with container queries, not to the window. A tutor panel is narrow on a wide screen.
  • Pick a layer by what the surface is: sticky, overlay, modal, popover, toast, tooltip. Never write z-index: 9999 or bump a number to win a fight; fix the stacking context instead.
  • Use logical properties (inset-inline-start, padding-inline) so layouts can mirror for right-to-left languages later.
  • Leave focus on .nu-* components alone. They draw their own indicator; the focus-ring tokens are for your own interactive elements.

Tailwind v4 also gets bg-overlay-gray and the other colours from tailwind-v4.css; there are no Tailwind names for the focus-ring width and offset in either version, so use the CSS variables.

In use

Structured lesson (D23)

From lg up, the lesson shows contents, lesson and tutor side by side. Narrower, the tutor becomes an overlay sheet and the contents a drawer, both on --nu-z-overlay. D23 approves this collapse; the exact switch point (around md–lg) is validated per layout. This site switches its own sidebar to a drawer at lg.

Photosynthesis · Biology 101

Plants turn light, water and carbon dioxide into glucose and oxygen.

Answers use this lesson as context.

1024px and wider: contents, lesson and tutor sit side by side.

The width is simulated with the toggle so the demo works on any screen. Opening a panel moves focus to its Close button; Escape or Close returns focus to the button that opened it. The frame uses isolation: isolate, so its overlay layers stay inside it.

Pending: mobile bottom navigation (D23)

The bottom-nav item set needs a product decision. Figma shows New · Chat · Library · Profile; the live app has New · Library · Spaces · Profile. There's no BottomNav pattern until the items are confirmed. Tablet layouts and the three-column collapse are on the Figma backlog.

Layering

Search opens the CommandMenu: its scrim sits on --nu-z-overlay (40) and the dialog on --nu-z-modal (50), both above this site's sticky header (--nu-z-sticky, 20). The menu uses --nu-z-popover (60).

Focus

Applied todayComponents use Figma's focus effect (--nu-shadow-buttons-*-focus). Tab to the button. Below 3:1 (C5).
Proposed (C5)The focus-ring tokens, drawn statically. Not applied to components until approved.
Site chromeThis site's own links and controls already use the focus-ring tokens. Tab to the link.

Accessibility

Layout decides whether content survives zoom and narrow screens, and whether the keyboard position can be seen.

CheckWCAGStatusWhat it takes
Reflow at 320 CSS px1.4.10 (AA)Partly verifiedContent fits one column at 320px wide (1280px at 400% zoom) with no sideways scrolling, except tables, code and matrices, which scroll inside their own focusable frame. Component pages are checked at 375px with no page scroll (template §7); a 320px check is still open. CommandMenu narrows to the window minus --nu-space-8.
Focus visible2.4.7 (AA)Gap (C5)Every focusable element shows an indicator. Components show Figma's focus effect, about 1.1:1 against white, so it's hard to see on filled buttons. The interim focus-ring colour measures 5.59:1 and meets the 3:1 non-text contrast the Stable gate requires.
Focus not obscured2.4.11 (AA)Mitigated on this siteA sticky header (--nu-z-sticky) can cover a focused element. This site sets scroll-padding-block-start to the header height plus --nu-space-6. Overlays on --nu-z-overlay and up must move focus into themselves and return it on close.
Forced colours1.4.11, 2.4.7Gap (C5)Figma's focus effects are box shadows, which Windows High Contrast removes. An outline (the focus-ring tokens) stays visible.

You must: never hide content at small widths that people need (move it into a drawer or sheet instead); keep focus order matching the visual order after a layout rearranges; never remove an outline without a visible replacement.

Guidelines and related

Use layout tokens for

  • Page-level rearrangement: columns becoming drawers and sheets.
  • Stacking any positioned surface: sticky bars, scrims, dialogs, menus.
  • Focus indicators on your own interactive elements.

Don't use them for

  • Components inside panels. Use container queries, not window breakpoints.
  • Space between elements. Use spacing tokens.
  • Overriding focus on .nu-* components, which draw their own.

Do and don't

DoMove panels into a sheet when space runs out. The tutor stays one tap away and keeps its focus handling.
Don'tDon't remove features on small screens. People on phones need the tutor too; hiding it fails them.
z-index: var(--nu-z-popover);
DoChoose a layer by role. The order between roles is fixed once, so surfaces can't fight.
z-index: 9999;
Don'tDon't escalate numbers. A bigger number wins today and breaks the next overlay.
DoDraw focus with the ring tokens. A strong colour with an offset is visible on filled and outlined controls alike.
Don'tDon't use a faint ring. A light grey ring hugging the control is easy to miss.