Getting Started
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.
All @nexub/* packages share one version; update them together.
pnpm add @nexub/react@^1.8 @nexub/patterns@^1.8 @nexub/themes@^1.8 @nexub/tokens@^1.8 @nexub/icons@^1.8Peer dependencies are unchanged: react and react-dom 18 or 19, and @nexub/tokens. Keep the Tailwind preset and content globs from Getting Started.
| Component | Before (1.5) | After (1.6+) | What to do |
|---|---|---|---|
| ToggleGroup, SegmentedControl | role="group", items with aria-pressed | single-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 lost | aria-disabled="true"; focus stays, clicks are blocked, the change is announced; width holds | Assert toHaveAttribute('aria-disabled', 'true') instead of toBeDisabled(). Don’t rely on focus moving away. |
| FileUpload dropzone | div role="button" with nested buttons | a native <label> wrapping the file input; one control named by its visible text | Query getByLabelText(…) (the visible text), not getByRole('button'). userEvent.upload on that input. |
| ActivityFeed | role="feed" | a plain list (ul / li) | Query getByRole('list') / getAllByRole('listitem'). |
| Avatar without name | role="img" with no name | aria-hidden (decorative) | Pass name when the avatar carries meaning; otherwise stop querying it by role. |
| Heading, Label, HelperText tone | inline style="color: …" | a colour class (no style attribute) | Snapshot / toHaveStyle checks on colour change; assert the class or computed colour. |
| Stack, Flex, Grid | inline style for gap, align, justify, cols, rows | Tailwind classes for on-scale values; off-scale values still use inline style | Update snapshots. Use scale values (gap={4}) to keep server HTML style-free. |
| Brand tones (leap, bookub, payub, founderub) | fell back to the default text colour | tint the text in Heading, Label, HelperText and EyebrowChip | Check contrast where you used a brand tone and expected neutral text. |
| Toaster | every toast listed | newest on top, older ones stacked behind; fan out on hover/focus; closed toasts leave the store | Pass expand to keep them fanned out; visibleToasts sets how many render (default 3). Tests that count toasts: count open ones. |
| tailwindcss-animate | components used animate-in / fade-in classes | components 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');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.
| Legacy | Role |
|---|---|
--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.
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.
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>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.
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.
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.
@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| Rule | What it does |
|---|---|
legacy-tokens | Legacy colour names → roles, in CSS variables and Tailwind classes. |
logical-classes | Physical Tailwind classes (ml-, pr-, left-, text-left…) → logical ones. |
tw-animate | Reports tailwindcss-animate classes to replace with nx-motion. Report only. |
opacity-modifier | Opacity 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.