Skip to content
nexubDesign System
v2.4.0

Getting Started

Upgrading from 1.5

For apps on @nexub/* 1.5.x. Nothing was removed and no prop was renamed, but 1.6 and 1.7 changed some markup and roles that tests and selectors see, and added conventions worth adopting. Every release since (1.8 through 2.x) is a rolling minor with no breaking changes, so this guide takes you straight to the current version.

1. Update

All @nexub/* packages share one version; update them together.

pnpm add @nexub/react@^2 @nexub/patterns@^2 @nexub/themes@^2 @nexub/tokens@^2 @nexub/icons@^2

Peer dependencies are unchanged: react and react-dom 18 or 19, and @nexub/tokens. Keep the Tailwind preset and content globs from Getting Started.

2. Fix tests and selectors

ComponentBefore (1.5)After (1.6+)What to do
ToggleGroup, SegmentedControlrole="group", items with aria-pressedsingle-select: role="radiogroup", items role="radio" + aria-checked; multi-select: role="toolbar"Query getByRole('radiogroup') / getByRole('radio', { name }) and assert aria-checked.
Button loading (and status="loading")native disabled; focus lostaria-disabled="true"; focus stays, clicks are blocked, the change is announced; width holdsAssert toHaveAttribute('aria-disabled', 'true') instead of toBeDisabled(). Don’t rely on focus moving away.
FileUpload dropzonediv role="button" with nested buttonsa native <label> wrapping the file input; one control named by its visible textQuery getByLabelText(…) (the visible text), not getByRole('button'). userEvent.upload on that input.
ActivityFeedrole="feed"a plain list (ul / li)Query getByRole('list') / getAllByRole('listitem').
Avatar without namerole="img" with no namearia-hidden (decorative)Pass name when the avatar carries meaning; otherwise stop querying it by role.
Heading, Label, HelperText toneinline style="color: …"a colour class (no style attribute)Snapshot / toHaveStyle checks on colour change; assert the class or computed colour.
Stack, Flex, Gridinline style for gap, align, justify, cols, rowsTailwind classes for on-scale values; off-scale values still use inline styleUpdate snapshots. Use scale values (gap={4}) to keep server HTML style-free.
Brand tones (leap, bookub, payub, founderub)fell back to the default text colourtint the text in Heading, Label, HelperText and EyebrowChipCheck contrast where you used a brand tone and expected neutral text.
Toasterevery toast listednewest on top, older ones stacked behind; fan out on hover/focus; closed toasts leave the storePass expand to keep them fanned out; visibleToasts sets how many render (default 3). Tests that count toasts: count open ones.
tailwindcss-animatecomponents used animate-in / fade-in classescomponents animate with nx-motion (themes/base.css)Nothing for components. The preset still registers the plugin for your own classes; prefer nx-motion.
// 1.5
screen.getByRole('group', { name: 'View' });
expect(screen.getByRole('button', { name: 'Save' })).toBeDisabled();

// 1.6+
screen.getByRole('radiogroup', { name: 'View' });
expect(screen.getByRole('button', { name: 'Save' })).toHaveAttribute('aria-disabled', 'true');

3. Adopt the new conventions

Colour roles

Use role names (--color-surface, --color-on-surface, --color-primary, *-container, outline…) and the Tailwind colours of the same name. The legacy names in @nexub/themes/light still work as aliases, so this can be gradual. Colors lists every role.

LegacyRole
--color-bg-default / -subtle / -muted--color-surface / -surface-container-low / -surface-container-high
--color-bg-raised, --color-surface-raised--color-surface-elevated
--color-text-primary / -secondary / -muted--color-on-surface / -on-surface-variant / -on-surface-muted
--color-text-link--color-primary
--color-border-default / -strong / -input--color-outline-variant / -outline / -outline
--color-semantic-{tone}-default--color-{tone}
--color-semantic-{tone}-bg / -strong--color-{tone}-container / -on-{tone}-container
--color-bg-inverse / --color-text-inverse--color-inverse-surface / -inverse-on-surface

Brand accents are roles too: --color-leap, --color-on-leap, --color-leap-container (same for bookub, payub, founderub). For charts use --color-chart-1..8 (categorical), chart-seq-1..7 and chart-div-1..7.

Logical classes for right-to-left

Components flip with dir; your layout should too. Replace ml-/mr- with ms-/me-, pl-/pr- with ps-/pe-, left-/right- with start-/end-, border-l/-r with border-s/-e, rounded-l/-r with rounded-s/-e, and text-left/-right with text-start/-end.

NexubProvider

One provider for locale (Intl formatting), dir, messages (every built-in string) and dateLocale (date-fns locale for the pickers). Optional; without it you get English and left-to-right. See Internationalization.

<NexubProvider locale="fr" messages={fr} dateLocale={frLocale}>
  {children}
</NexubProvider>

Strict CSP

Server HTML from layout and text components carries no style attribute when you use scale values, so style-src 'self' works without 'unsafe-inline'. Pass scrollLock="class" to Sheet/Drawer, and a nonce to ThemeScript. Details in Strict CSP.

Motion tokens

Animate your own overlays with nx-motion nx-pop (or nx-dialog) on Radix's data-state instead of animate-in classes, and time transitions with the spring tokens: duration-spatial-default ease-spatial-default for movement, duration-effects-fast ease-effects-fast for opacity and colour. Exits should be faster than enters, and every animation needs a motion-reduce: fallback. See Motion & interaction.

Touch targets

Button, IconButton, Checkbox, Radio and Switch get a 44×44px hit area on touch screens. Add nx-hit to your own small controls and keep 44px between their centres.

4. Automate it: nexub-migrate

@nexub/tokens 1.8.0 ships a codemod. It is a dry run by default and prints what it would change.

npx nexub-migrate src           # dry run: report changes
npx nexub-migrate src --write   # apply them
npx nexub-migrate src --check   # CI: exit 1 if anything is left
RuleWhat it does
legacy-tokensLegacy colour names → roles, in CSS variables and Tailwind classes.
logical-classesPhysical Tailwind classes (ml-, pr-, left-, text-left…) → logical ones.
tw-animateReports tailwindcss-animate classes to replace with nx-motion. Report only.
opacity-modifierOpacity modifiers on role colours (bg-primary/50, bg-[var(--color-primary)]/50), which Tailwind 3 silently drops.

Run your tests after --write, and review the diff: it rewrites class strings, not logic.