nxds with Chakra UI
Add nxds components to a Chakra UI app, and keep Chakra for everything else. These steps use a Next.js App Router app on Chakra UI 3.
What nxds adds for you
nxds gives a Chakra app parts it doesn't have, themed from one appearance object. Introduction covers the theme engine, the copy-and-own source and the agent support.
| You need | nxds part |
|---|---|
| A command palette | CommandDialog |
| An app sidebar | Sidebar and SidebarProvider |
| A date picker | DatePicker |
| A chart | ChartContainer |
| A filter bar | FilterBuilder |
| A select with several values | MultiSelect |
| A menu bar or right-click menu | Menubar and ContextMenu |
| A carousel | Carousel |
| A chat thread | Message and Bubble |
Add your first nxds component
Set up nxds, then add a component:
init adds the provider and the theme script to app/layout.tsx, and creates
app/nxds.css. Chakra's reset sets box-sizing, so delete the Preflight
lines in app/nxds.css, as
Add nxds to an existing app
explains.
Chakra puts its CSS in four cascade layers: reset, base, tokens and
recipes. It declares them after nxds loads, so its reset would win over
nx: classes and strip an nxds button's padding. Name every layer at the top
of globals.css, so Chakra's sit below the nxds utilities:
Chakra's layers keep their own order, so your Chakra components look the same.
Render the nxds button on an existing screen, beside Chakra's:
Chakra's overlays sit at z-index 1300 to 1800. Raise the nxds layers above them, so an nxds popup opens over a Chakra dialog:
Inside an nxds dialog, render Chakra popups in place: leave out their
<Portal>, or pass <Portal disabled>.
On Next.js 16, Chakra asks you to add --webpack to your dev and build
scripts: next dev --webpack and next build --webpack. Turbopack hydrates
Chakra's styles out of order, which logs a hydration error. See
Chakra's Next.js guide.
Share light and dark mode
nxds owns the mode. Chakra reads the .dark class through next-themes, the
same class nxds writes. Make next-themes follow nxds in the provider that
Chakra's CLI created:
Put it inside the nxds provider:
next-themes uses 'light', 'dark' and 'system', like nxds. Its
defaultTheme matches the nxds default, so a first visit paints one mode. If
next-themes saved another mode before you added nxds, that mode can show for
a moment on the first load. From then on, both save the same mode.
next-themes has no option to stop saving its own copy, and it syncs that
copy across tabs. A toggle in another tab switches the .dark class here, and
the nxds state catches up on the next reload.
Switch the mode through nxds only, not Chakra's setColorMode:
useColorMode and useColorModeValue keep working, because they read
next-themes.
Match your theme
| Chakra theme | Appearance field | How to match |
|---|---|---|
colors.brand, your colorPalette | brandColor | Use the shade your solid buttons show. nxds builds the hover and pressed shades |
colors.gray | surfaceTone | Chakra's default gray is the same scale as zinc |
radii.l2 | corners | 0 is square, 0.25rem is subtle, 0.5rem is smooth, 0.75rem is round, 1rem is extra-round. Chakra's default l2 is 0.25rem |
spacing | density | Compare in the Playground |
shadows | elevation | Compare in the Playground. flat turns shadows off |
| None | stroke | Chakra has no border width setting, so keep normal |
fonts.body | prefs.uiFont | Chakra's default is Inter |
| The color mode | mode | nxds owns it, as Share light and dark mode shows |
Where nxds fits
Chakra 3 passes a details object to its handlers, such as
onValueChange({ value }). nxds handlers take the value itself:
onValueChange(value), onCheckedChange(checked), onOpenChange(open).
| Chakra UI | nxds | What changes |
|---|---|---|
Button | Button | variant solid, subtle, outline, ghost, plain → default, secondary, outline, ghost, link |
Button colorPalette="red" | Button variant="destructive" | nxds has no colorPalette. The brand comes from the appearance |
IconButton | Button size="icon" | Give it an aria-label |
Group attached | ButtonGroup | |
Field.Root, Field.Label, Field.HelperText, Field.ErrorText | Field, FieldLabel, FieldDescription, FieldError | invalid → data-invalid on Field and aria-invalid on the input |
Field.RequiredIndicator | FieldRequiredIndicator | |
Fieldset.Root, Fieldset.Legend, Fieldset.Content | FieldSet, FieldLegend, FieldGroup | |
Input, Textarea | Input, Textarea | |
InputGroup | InputGroup, InputGroupAddon, InputGroupInput | startElement and endElement → InputGroupAddon |
Checkbox.Root | Checkbox with a FieldLabel | onCheckedChange(details) → onCheckedChange(checked) |
Switch.Root | Switch | onCheckedChange(details) → onCheckedChange(checked) |
RadioGroup.Root, RadioGroup.Item | RadioGroup, RadioGroupItem | onValueChange(details) → onValueChange(value) |
CheckboxCard, RadioCard | ChoiceCard | |
Select.Root with a collection | Select, SelectTrigger, SelectValue, SelectContent, SelectItem | Items are children, not a collection. value is a string, not an array |
Select.Root multiple | MultiSelect | onValueChange(details) → onValuesChange(values) |
NativeSelect | NativeSelect, NativeSelectOption | |
Combobox | Combobox | Compose a ComboboxTrigger, ComboboxInput and ComboboxItem list |
PinInput | InputOTP, InputOTPGroup, InputOTPSlot | |
Slider | Slider | onValueChange(details) → onValueChange(value) |
SegmentGroup | ToggleGroup type="single" | |
Dialog.Root, Dialog.Content, Dialog.Header, Dialog.Body, Dialog.Footer | Dialog, DialogContent, DialogHeader, DialogBody, DialogFooter | onOpenChange(details) → onOpenChange(open) |
Dialog asking to confirm | AlertDialog | |
Drawer | Sheet, or Drawer for a bottom sheet | placement → side on SheetContent |
Menu | DropdownMenu | |
Popover, HoverCard, Tooltip | Popover, HoverCard, Tooltip | Popover.Trigger → PopoverTrigger, Popover.Content → PopoverContent |
Toaster, createToaster | Toaster, toast() | Render <Toaster /> once, then call toast('Saved') |
Alert.Root, Alert.Title, Alert.Description | Alert, AlertTitle, AlertDescription | status error, warning, info, success → variant destructive, warning, information, success |
Accordion.Root, Accordion.Item, Accordion.ItemTrigger, Accordion.ItemContent | Accordion, AccordionItem, AccordionTrigger, AccordionContent | multiple → type="multiple" |
Collapsible | Collapsible, CollapsibleTrigger, CollapsibleContent | |
Tabs.Root, Tabs.List, Tabs.Trigger, Tabs.Content | Tabs, TabsList, TabsTrigger, TabsContent | onValueChange(details) → onValueChange(value) |
Breadcrumb | Breadcrumb, BreadcrumbList, BreadcrumbItem, BreadcrumbLink | |
Pagination | Pagination, PaginationContent, PaginationLink | |
Card.Root, Card.Header, Card.Body, Card.Footer | Card, CardHeader, CardContent, CardFooter | Card.Title → CardTitle, Card.Description → CardDescription |
Avatar, AvatarGroup | Avatar, AvatarImage, AvatarFallback, AvatarGroup | |
Badge | Badge | colorPalette → variant. variant="outline" → fill="outline" |
Tag with a close button | FilterChip | |
Separator | Separator | |
Progress | Progress | |
Spinner, Skeleton | Spinner, Skeleton | |
Table.Root, Table.Header, Table.Body, Table.Row, Table.ColumnHeader, Table.Cell | Table, TableHeader, TableBody, TableRow, TableHead, TableCell | |
EmptyState | EmptyState, EmptyStateHeader, EmptyStateTitle, EmptyStateDescription | |
DataList | DescriptionList, DescriptionListItem, DescriptionListTerm, DescriptionListDescription | |
Kbd, ScrollArea | Kbd, ScrollArea | |
Splitter | ResizablePanelGroup, ResizablePanel, ResizableHandle | |
Box, Flex, Stack, Grid, Container | nx: flex and grid classes | At the default density, gap="4" → nx:gap-4 and p="4" → nx:p-4 |
Text, Heading | nx:typography-* classes | Such as nx:typography-heading-large and nx:typography-body-default |
hideBelow, hideFrom, useBreakpointValue | Show and Hide | hideBelow="md" → <Hide below="md"> |
Swap a component (optional)
Swap a component when you touch the screen anyway. This form field moves from Chakra to nxds:
To check the nx: classes in the files you move over, add the
nxds lint rules.
What stays on Chakra UI
nxds has no part for these. Keep them on Chakra:
StepsRatingGroupColorPickerandColorSwatchFileUploadEditableNumberInputPasswordInputProgressCircleStatTimelineTreeViewQrCode,Clipboard,MarqueeandActionBar