Radius
Radius tokens set the corner shape of Nuton's surfaces, from square to pill. Use them for every border-radius; never type a pixel value.
Beta because all ten radius tokens are interim (src/interim). The Nuton DS file has no local radius variables: screens bind the remote 04-Radius library (Q-02, TD-14), so the values here are the observed set. Stable needs a local Radius collection in Figma (D9 backlog, D20); the tokens then move into the Figma snapshot and leave src/interim.
Overview
Nuton is soft by default: pill buttons, round avatars and generously rounded fields and menus. Radius is a scale of ten steps; a component picks one step per part, and nested parts step down so corners stay concentric.
0 · 0px4 · 4px6 · 6px8 · 8px10 · 10px12 · 12px16 · 16px20 · 20px24 · 24pxfull · 9999px<div style={{ borderRadius: 'var(--nu-radius-0)' }} />
<div style={{ borderRadius: 'var(--nu-radius-4)' }} />
<div style={{ borderRadius: 'var(--nu-radius-6)' }} />
<div style={{ borderRadius: 'var(--nu-radius-8)' }} />
<div style={{ borderRadius: 'var(--nu-radius-10)' }} />
<div style={{ borderRadius: 'var(--nu-radius-12)' }} />
<div style={{ borderRadius: 'var(--nu-radius-16)' }} />
<div style={{ borderRadius: 'var(--nu-radius-20)' }} />
<div style={{ borderRadius: 'var(--nu-radius-24)' }} />
<div style={{ borderRadius: 'var(--nu-radius-full)' }} />- Pill and circle (
radius-full): buttons, avatars, status dots, key icons. The default Button corner. - Large (
radius-16toradius-24): floating containers and large fields: menus, the command menu, 48px inputs, status modals and marketing cards. - Medium (
radius-8toradius-12): compact controls and items inside containers: small inputs, menu items, rounded buttons, textareas. - Small (
radius-4,radius-6): tiny parts: keyboard keys, checkbox indicators, the password reveal button. - Square (
radius-0):corners="sharp"on Button and Button group, and edges that meet another surface.
Tokens
Every radius token, generated from @nuton/tokens. All ten are interim: design values waiting for a home in the Nuton Figma file.
| Preview | Token | Value | Source |
|---|---|---|---|
--nu-radius-0 | 0px | Interim | |
--nu-radius-4 | 4px | Interim | |
--nu-radius-6 | 6px | Interim | |
--nu-radius-8 | 8px | Interim | |
--nu-radius-10 | 10px | Interim | |
--nu-radius-12 | 12px | Interim | |
--nu-radius-16 | 16px | Interim | |
--nu-radius-20 | 20px | Interim | |
--nu-radius-24 | 24px | Interim | |
--nu-radius-full | 9999px | Interim |
Values are the set observed on Nuton screens: the remote 04-Radius library, the teacher app's tokens and Figma screen usage (radius-20 and radius-24 come from screens only). radius-full is a large number rather than 50%, so a pill stays a pill at any width.
Usage
/* Load once: import '@nuton/tokens/tokens.css' */
.lesson-card {
border-radius: var(--nu-radius-16);
}
.lesson-card__thumbnail {
border-radius: var(--nu-radius-8); /* steps down inside the card */
}
.space-badge {
border-radius: var(--nu-radius-full);
}/* globals.css */
@import "tailwindcss";
@import "@nuton/tokens/tokens.css";
@import "@nuton/tokens/tailwind-v4-theme.css"; /* adds --radius-0 … --radius-24, --radius-full */
// Utilities: rounded-0 rounded-4 rounded-6 rounded-8 rounded-10 rounded-12
// rounded-16 rounded-20 rounded-24 rounded-full
<article className="rounded-16 border border-stroke-soft-200">…</article>// tailwind.config.js
module.exports = {
presets: [require('@nuton/tokens/tailwind-v3-preset')], // extends theme.borderRadius
}
// Same keys: rounded-0 … rounded-24, rounded-full
<article className="rounded-16">…</article>Both Tailwind outputs add Nuton's numeric keys next to Tailwind's own (rounded-lg and friends still exist). Use the numeric keys so every corner traces back to a token.
Choosing a radius
- Use the component's radius. Components already set theirs. Only reach for a token when you build a new surface.
- Nest by stepping down. An inner surface uses a smaller radius than its container, roughly the outer radius minus the padding between them: a menu at
radius-16holds items atradius-8. - Scale with size. Taller controls take larger radii: a 48px input uses
radius-16, a 36px oneradius-8. - One corner style per surface. Don't mix smooth, rounded and sharp buttons in one view.
- Pills use
radius-full. Never50%or a large hand-typed number.
Radius by component
| Component | Part | Token |
|---|---|---|
| Button | corners="smooth" (default) | radius-full |
| Button | corners="rounded"; small icon-only | radius-10 · radius-8 |
| Button | corners="sharp" | radius-0 |
| Button group | smooth · rounded (2xs) · sharp | radius-full · radius-8 (radius-6) · radius-0 |
| Input | size="lg" (default, 48px tall) | radius-16 |
| Input | size="sm" (36px) and appearance="inline" | radius-8 |
| Password input | Reveal button | radius-6 |
| Textarea | Box | radius-12 |
| Digit input | Each digit box | radius-10 |
| Dropdown, Combobox, Command menu | Menu container | radius-16 |
| Dropdown, Combobox | Item sm · item lg | radius-8 · radius-10 |
| Command menu | Item | radius-10 |
| Menu items | Checkbox indicator · leading media and switch | radius-4 · radius-full |
| Kbd | Key cap | radius-4 |
| Avatar | Image, initials, status badges | radius-full |
| Chart legend, key icon | Dot and icon frame | radius-full |
Read from packages/ui/src/components/*/*.css. Two parts don't use a token yet: the checkbox indicator's inner mark (a fixed 2.6px, --nu-checkbox-inner-radius) and anything with a radius not on this scale.
In use
Button corners
The corners prop on Button and Button group picks the radius. Smooth is the default and Nuton's look.
<Button corners="smooth">Start lesson</Button> // radius-full
<Button corners="rounded">Start lesson</Button> // radius-10
<Button corners="sharp">Start lesson</Button> // radius-0<ButtonGroup aria-label="Lesson view" size="sm" corners="smooth">…</ButtonGroup> // radius-full
<ButtonGroup aria-label="Lesson view" size="sm" corners="rounded">…</ButtonGroup> // radius-8Fields
Input radius follows its size: radius-16 at the default 48px, radius-8 at 36px. Textarea uses radius-12.
<Field label="Search lessons">
<Input type="search" placeholder="Photosynthesis" leadingIcon={<RiSearchLine />} /> // radius-16
</Field>
<Field label="Space name">
<Input size="sm" placeholder="Biology 101" /> // radius-8
</Field>
<Field label="Lesson summary">
<Textarea rows={3} placeholder="What will learners take away?" /> // radius-12
</Field>Menus
Open the menu. The container uses radius-16 and its padding holds items at radius-8, so the hover fill sits concentric with the container's corner.
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button size="sm" appearance="stroke" trailingIcon={<RiArrowDownSLine />}>Lesson actions</Button>
</DropdownMenuTrigger>
<DropdownMenuContent> {/* radius-16 */}
<DropdownMenuLabel>Photosynthesis</DropdownMenuLabel>
<DropdownMenuItem leading={<RiEditLine />}>Rename</DropdownMenuItem> {/* radius-8 */}
<DropdownMenuItem leading={<RiFileCopyLine />}>Duplicate</DropdownMenuItem>
<DropdownMenuItem leading={<RiShareLine />}>Share with space</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem leading={<RiDeleteBinLine />}>Delete lesson</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>Pills and circles
<Avatar name="Amara Okafor" color="purple" size={40} bottomStatus="online" bottomStatusLabel="Online" />
<Avatar name="Tunde Bello" color="sky" size={40} />
<ChartLegend color="blue">Completed lessons</ChartLegend>
<Kbd>⌘K</Kbd> // radius-4: a key cap, not a pillAccessibility
Radius never carries meaning
Corner shape isn't a state or a message. Errors, selection and focus have their own colour, border, text and ARIA cues; a square corner never means “error” or “disabled”. Don't refer to controls by shape in copy (“press the round button”), because shape isn't available to everyone (WCAG 1.3.3).
Focus rings follow shape
Button, Input and Digit input draw focus as a box-shadow, so the ring takes the component's radius: a pill ring on a pill button, a radius-16 ring on a large input. Button group and Link button keep the browser outline (C10), which also follows border-radius in current browsers. The ring's contrast is the open issue, not its shape: the Figma focus effect measures about 1.1:1 against white (C5).
Forced colours and clipping
In Windows High Contrast, Button and Digit input switch to a 2px Highlight outline, which keeps the shape. Containers that clip (overflow: hidden on Input and Button group) cut off anything in their corners, so keep content inside the padding; on Button group this may clip an edge item's focus outline (C29).
You must: keep a non-shape cue for every state; check focus rings on new rounded surfaces aren't clipped by a parent with overflow: hidden.
Guidelines and related
Use radius tokens for
- Every
border-radiusin product code and new components. - Nesting: a smaller step for each surface inside another.
- Pills and circles, with
radius-full.
Don't use radius for
- Signalling state, priority or errors.
- Overriding a component's own radius. Use its
cornersorsizeprop. - Values off the scale (
7px,50%). Ask the DS Owner to add a step in Figma instead.
Do and don't
Adding a radius
Radius is a design token, so new steps start in Figma (D20). Until the Nuton file has a local Radius collection, propose new values to the DS Owner; they're added to src/interim with the nuton.interim flag and reported on every token check.