Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
title: February 2026 - Unified Radix UI and Multi-Base Architecture
description: Migrated to the unified radix-ui package, introduced multi-base component architecture, and optimized utility components.
date: 2026-02-10
date: 2026-02-16
---

We've shipped several major improvements to DiceUI's component infrastructure and documentation.
Expand Down
2 changes: 1 addition & 1 deletion docs/public/r/styles/radix-default/action-bar-demo.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
"files": [
{
"path": "examples/action-bar-demo.tsx",
"content": "\"use client\";\n\nimport { Copy, Trash2, X } from \"lucide-react\";\nimport * as React from \"react\";\nimport { Checkbox } from \"@/components/ui/checkbox\";\nimport { Label } from \"@/components/ui/label\";\nimport { cn } from \"@/lib/utils\";\nimport {\n ActionBar,\n ActionBarClose,\n ActionBarGroup,\n ActionBarItem,\n ActionBarSelection,\n ActionBarSeparator,\n} from \"@/registry/bases/radix/ui/action-bar\";\n\ninterface Task {\n id: string;\n name: string;\n}\n\nexport default function ActionBarDemo() {\n const [tasks, setTasks] = React.useState<Task[]>([\n { id: crypto.randomUUID(), name: \"Weekly Status Report\" },\n { id: crypto.randomUUID(), name: \"Client Invoice Review\" },\n { id: crypto.randomUUID(), name: \"Product Roadmap\" },\n { id: crypto.randomUUID(), name: \"Team Standup Notes\" },\n ]);\n const [selectedTaskIds, setSelectedTaskIds] = React.useState<Set<string>>(\n new Set(),\n );\n\n const open = selectedTaskIds.size > 0;\n\n const onOpenChange = React.useCallback((open: boolean) => {\n if (!open) {\n setSelectedTaskIds(new Set());\n }\n }, []);\n\n const onItemSelect = React.useCallback(\n (id: string, checked: boolean) => {\n const newSelected = new Set(selectedTaskIds);\n if (checked) {\n newSelected.add(id);\n } else {\n newSelected.delete(id);\n }\n setSelectedTaskIds(newSelected);\n },\n [selectedTaskIds],\n );\n\n const onDuplicate = React.useCallback(() => {\n const selectedItems = tasks.filter((task) => selectedTaskIds.has(task.id));\n const duplicates = selectedItems.map((task) => ({\n ...task,\n id: crypto.randomUUID(),\n name: `${task.name} (copy)`,\n }));\n setTasks([...tasks, ...duplicates]);\n setSelectedTaskIds(new Set());\n }, [tasks, selectedTaskIds]);\n\n const onDelete = React.useCallback(() => {\n setTasks(tasks.filter((task) => !selectedTaskIds.has(task.id)));\n setSelectedTaskIds(new Set());\n }, [tasks, selectedTaskIds]);\n\n return (\n <div className=\"flex w-full flex-col gap-2.5\">\n <h3 className=\"font-semibold text-lg\">Tasks</h3>\n <div className=\"flex max-h-72 flex-col gap-1.5 overflow-y-auto\">\n {tasks.map((task) => (\n <Label\n key={task.id}\n className={cn(\n \"flex cursor-pointer items-center gap-2.5 rounded-md border bg-card/70 px-3 py-2.5 transition-colors hover:bg-accent/70\",\n selectedTaskIds.has(task.id) && \"bg-accent/70\",\n )}\n >\n <Checkbox\n checked={selectedTaskIds.has(task.id)}\n onCheckedChange={(checked) =>\n onItemSelect(task.id, checked === true)\n }\n />\n <span className=\"truncate font-medium text-sm\">{task.name}</span>\n </Label>\n ))}\n </div>\n\n <ActionBar open={open} onOpenChange={onOpenChange}>\n <ActionBarSelection>\n {selectedTaskIds.size} selected\n <ActionBarSeparator />\n <ActionBarClose>\n <X />\n </ActionBarClose>\n </ActionBarSelection>\n <ActionBarSeparator />\n <ActionBarGroup>\n <ActionBarItem onSelect={onDuplicate}>\n <Copy />\n Duplicate\n </ActionBarItem>\n <ActionBarItem variant=\"destructive\" onSelect={onDelete}>\n <Trash2 />\n Delete\n </ActionBarItem>\n </ActionBarGroup>\n </ActionBar>\n </div>\n );\n}\n",
"content": "\"use client\";\r\n\r\nimport { Copy, Trash2, X } from \"lucide-react\";\r\nimport * as React from \"react\";\r\nimport { Checkbox } from \"@/components/ui/checkbox\";\r\nimport { Label } from \"@/components/ui/label\";\r\nimport { cn } from \"@/lib/utils\";\r\nimport {\r\n ActionBar,\r\n ActionBarClose,\r\n ActionBarGroup,\r\n ActionBarItem,\r\n ActionBarSelection,\r\n ActionBarSeparator,\r\n} from \"@/registry/bases/radix/ui/action-bar\";\r\n\r\ninterface Task {\r\n id: string;\r\n name: string;\r\n}\r\n\r\nexport default function ActionBarDemo() {\r\n const [tasks, setTasks] = React.useState<Task[]>([\r\n { id: crypto.randomUUID(), name: \"Weekly Status Report\" },\r\n { id: crypto.randomUUID(), name: \"Client Invoice Review\" },\r\n { id: crypto.randomUUID(), name: \"Product Roadmap\" },\r\n { id: crypto.randomUUID(), name: \"Team Standup Notes\" },\r\n ]);\r\n const [selectedTaskIds, setSelectedTaskIds] = React.useState<Set<string>>(\r\n new Set(),\r\n );\r\n\r\n const open = selectedTaskIds.size > 0;\r\n\r\n const onOpenChange = React.useCallback((open: boolean) => {\r\n if (!open) {\r\n setSelectedTaskIds(new Set());\r\n }\r\n }, []);\r\n\r\n const onItemSelect = React.useCallback(\r\n (id: string, checked: boolean) => {\r\n const newSelected = new Set(selectedTaskIds);\r\n if (checked) {\r\n newSelected.add(id);\r\n } else {\r\n newSelected.delete(id);\r\n }\r\n setSelectedTaskIds(newSelected);\r\n },\r\n [selectedTaskIds],\r\n );\r\n\r\n const onDuplicate = React.useCallback(() => {\r\n const selectedItems = tasks.filter((task) => selectedTaskIds.has(task.id));\r\n const duplicates = selectedItems.map((task) => ({\r\n ...task,\r\n id: crypto.randomUUID(),\r\n name: `${task.name} (copy)`,\r\n }));\r\n setTasks([...tasks, ...duplicates]);\r\n setSelectedTaskIds(new Set());\r\n }, [tasks, selectedTaskIds]);\r\n\r\n const onDelete = React.useCallback(() => {\r\n setTasks(tasks.filter((task) => !selectedTaskIds.has(task.id)));\r\n setSelectedTaskIds(new Set());\r\n }, [tasks, selectedTaskIds]);\r\n\r\n return (\r\n <div className=\"flex w-full flex-col gap-2.5\">\r\n <h3 className=\"font-semibold text-lg\">Tasks</h3>\r\n <div className=\"flex max-h-72 flex-col gap-1.5 overflow-y-auto\">\r\n {tasks.map((task) => (\r\n <Label\r\n key={task.id}\r\n className={cn(\r\n \"flex cursor-pointer items-center gap-2.5 rounded-md border bg-card/70 px-3 py-2.5 transition-colors hover:bg-accent/70\",\r\n selectedTaskIds.has(task.id) && \"bg-accent/70\",\r\n )}\r\n >\r\n <Checkbox\r\n checked={selectedTaskIds.has(task.id)}\r\n onCheckedChange={(checked) =>\r\n onItemSelect(task.id, checked === true)\r\n }\r\n />\r\n <span className=\"truncate font-medium text-sm\">{task.name}</span>\r\n </Label>\r\n ))}\r\n </div>\r\n\r\n <ActionBar open={open} onOpenChange={onOpenChange}>\r\n <ActionBarSelection>\r\n {selectedTaskIds.size} selected\r\n <ActionBarSeparator />\r\n <ActionBarClose>\r\n <X />\r\n </ActionBarClose>\r\n </ActionBarSelection>\r\n <ActionBarSeparator />\r\n <ActionBarGroup>\r\n <ActionBarItem onSelect={onDuplicate}>\r\n <Copy />\r\n Duplicate\r\n </ActionBarItem>\r\n <ActionBarItem variant=\"destructive\" onSelect={onDelete}>\r\n <Trash2 />\r\n Delete\r\n </ActionBarItem>\r\n </ActionBarGroup>\r\n </ActionBar>\r\n </div>\r\n );\r\n}\r\n",
"type": "registry:example",
"target": ""
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
"files": [
{
"path": "examples/action-bar-position-demo.tsx",
"content": "\"use client\";\n\nimport { Archive, Star, X } from \"lucide-react\";\nimport * as React from \"react\";\nimport { Label } from \"@/components/ui/label\";\nimport {\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from \"@/components/ui/select\";\nimport { Switch } from \"@/components/ui/switch\";\nimport {\n ActionBar,\n ActionBarClose,\n ActionBarGroup,\n ActionBarItem,\n ActionBarSelection,\n ActionBarSeparator,\n} from \"@/registry/bases/radix/ui/action-bar\";\n\nexport default function ActionBarPositionDemo() {\n const [open, setOpen] = React.useState(false);\n const [side, setSide] = React.useState<\"top\" | \"bottom\">(\"bottom\");\n const [align, setAlign] = React.useState<\"start\" | \"center\" | \"end\">(\n \"center\",\n );\n\n return (\n <div className=\"flex flex-col gap-4\">\n <div className=\"flex items-center gap-2\">\n <Switch id=\"open\" checked={open} onCheckedChange={setOpen} />\n <Label htmlFor=\"open\">Show Action Bar</Label>\n </div>\n <div className=\"flex items-center gap-2\">\n <Label htmlFor=\"side\" className=\"w-14\">\n Side\n </Label>\n <Select\n value={side}\n onValueChange={(value) => setSide(value as \"top\" | \"bottom\")}\n >\n <SelectTrigger id=\"side\" className=\"w-28\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value=\"top\">Top</SelectItem>\n <SelectItem value=\"bottom\">Bottom</SelectItem>\n </SelectContent>\n </Select>\n </div>\n <div className=\"flex items-center gap-2\">\n <Label htmlFor=\"align\" className=\"w-14\">\n Align\n </Label>\n <Select\n value={align}\n onValueChange={(value) =>\n setAlign(value as \"start\" | \"center\" | \"end\")\n }\n >\n <SelectTrigger id=\"align\" className=\"w-28\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value=\"start\">Start</SelectItem>\n <SelectItem value=\"center\">Center</SelectItem>\n <SelectItem value=\"end\">End</SelectItem>\n </SelectContent>\n </Select>\n </div>\n\n <ActionBar open={open} onOpenChange={setOpen} side={side} align={align}>\n <ActionBarSelection>\n 3 selected\n <ActionBarSeparator />\n <ActionBarClose>\n <X />\n </ActionBarClose>\n </ActionBarSelection>\n <ActionBarSeparator />\n <ActionBarGroup>\n <ActionBarItem>\n <Star />\n Favorite\n </ActionBarItem>\n <ActionBarItem>\n <Archive />\n Archive\n </ActionBarItem>\n </ActionBarGroup>\n </ActionBar>\n </div>\n );\n}\n",
"content": "\"use client\";\r\n\r\nimport { Archive, Star, X } from \"lucide-react\";\r\nimport * as React from \"react\";\r\nimport { Label } from \"@/components/ui/label\";\r\nimport {\r\n Select,\r\n SelectContent,\r\n SelectItem,\r\n SelectTrigger,\r\n SelectValue,\r\n} from \"@/components/ui/select\";\r\nimport { Switch } from \"@/components/ui/switch\";\r\nimport {\r\n ActionBar,\r\n ActionBarClose,\r\n ActionBarGroup,\r\n ActionBarItem,\r\n ActionBarSelection,\r\n ActionBarSeparator,\r\n} from \"@/registry/bases/radix/ui/action-bar\";\r\n\r\nexport default function ActionBarPositionDemo() {\r\n const [open, setOpen] = React.useState(false);\r\n const [side, setSide] = React.useState<\"top\" | \"bottom\">(\"bottom\");\r\n const [align, setAlign] = React.useState<\"start\" | \"center\" | \"end\">(\r\n \"center\",\r\n );\r\n\r\n return (\r\n <div className=\"flex flex-col gap-4\">\r\n <div className=\"flex items-center gap-2\">\r\n <Switch id=\"open\" checked={open} onCheckedChange={setOpen} />\r\n <Label htmlFor=\"open\">Show Action Bar</Label>\r\n </div>\r\n <div className=\"flex items-center gap-2\">\r\n <Label htmlFor=\"side\" className=\"w-14\">\r\n Side\r\n </Label>\r\n <Select\r\n value={side}\r\n onValueChange={(value) => setSide(value as \"top\" | \"bottom\")}\r\n >\r\n <SelectTrigger id=\"side\" className=\"w-28\">\r\n <SelectValue />\r\n </SelectTrigger>\r\n <SelectContent>\r\n <SelectItem value=\"top\">Top</SelectItem>\r\n <SelectItem value=\"bottom\">Bottom</SelectItem>\r\n </SelectContent>\r\n </Select>\r\n </div>\r\n <div className=\"flex items-center gap-2\">\r\n <Label htmlFor=\"align\" className=\"w-14\">\r\n Align\r\n </Label>\r\n <Select\r\n value={align}\r\n onValueChange={(value) =>\r\n setAlign(value as \"start\" | \"center\" | \"end\")\r\n }\r\n >\r\n <SelectTrigger id=\"align\" className=\"w-28\">\r\n <SelectValue />\r\n </SelectTrigger>\r\n <SelectContent>\r\n <SelectItem value=\"start\">Start</SelectItem>\r\n <SelectItem value=\"center\">Center</SelectItem>\r\n <SelectItem value=\"end\">End</SelectItem>\r\n </SelectContent>\r\n </Select>\r\n </div>\r\n\r\n <ActionBar open={open} onOpenChange={setOpen} side={side} align={align}>\r\n <ActionBarSelection>\r\n 3 selected\r\n <ActionBarSeparator />\r\n <ActionBarClose>\r\n <X />\r\n </ActionBarClose>\r\n </ActionBarSelection>\r\n <ActionBarSeparator />\r\n <ActionBarGroup>\r\n <ActionBarItem>\r\n <Star />\r\n Favorite\r\n </ActionBarItem>\r\n <ActionBarItem>\r\n <Archive />\r\n Archive\r\n </ActionBarItem>\r\n </ActionBarGroup>\r\n </ActionBar>\r\n </div>\r\n );\r\n}\r\n",
"type": "registry:example",
"target": ""
}
Expand Down
4 changes: 2 additions & 2 deletions docs/public/r/styles/radix-default/action-bar.json

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
"files": [
{
"path": "examples/angle-slider-controlled-demo.tsx",
"content": "\"use client\";\n\nimport { RotateCcwIcon, ShuffleIcon } from \"lucide-react\";\nimport { animate } from \"motion/react\";\nimport * as React from \"react\";\nimport { Button } from \"@/components/ui/button\";\nimport {\n AngleSlider,\n AngleSliderRange,\n AngleSliderThumb,\n AngleSliderTrack,\n AngleSliderValue,\n} from \"@/registry/bases/radix/ui/angle-slider\";\n\nexport default function AngleSliderControlledDemo() {\n const [value, setValue] = React.useState([180]);\n const animationRef = React.useRef<ReturnType<typeof animate> | null>(null);\n\n const animateToValue = React.useCallback(\n (targetValue: number) => {\n if (animationRef.current) {\n animationRef.current.stop();\n }\n\n const currentValue = value[0] ?? 0;\n\n let diff = targetValue - currentValue;\n if (diff > 180) {\n diff -= 360;\n } else if (diff < -180) {\n diff += 360;\n }\n\n animationRef.current = animate(0, diff, {\n duration: 0.4,\n ease: [0.25, 0.46, 0.45, 0.94],\n onUpdate: (progress: number) => {\n const animatedValue = currentValue + progress;\n const normalizedValue = Math.round(\n ((animatedValue % 360) + 360) % 360,\n );\n setValue([normalizedValue]);\n },\n onComplete: () => {\n setValue([targetValue]);\n animationRef.current = null;\n },\n });\n },\n [value],\n );\n\n const onReset = React.useCallback(() => {\n animateToValue(0);\n }, [animateToValue]);\n\n const onRandomize = React.useCallback(() => {\n animateToValue(Math.floor(Math.random() * 360));\n }, [animateToValue]);\n\n React.useEffect(() => {\n return () => {\n if (animationRef.current) {\n animationRef.current.stop();\n }\n };\n }, []);\n\n return (\n <div className=\"flex flex-col gap-4\">\n <div className=\"flex items-center gap-3\">\n <Button variant=\"outline\" size=\"sm\" onClick={onReset}>\n <RotateCcwIcon />\n Reset\n </Button>\n <Button size=\"sm\" onClick={onRandomize}>\n <ShuffleIcon />\n Randomize\n </Button>\n </div>\n <AngleSlider\n value={value}\n onValueChange={setValue}\n max={360}\n min={0}\n step={1}\n size={80}\n >\n <AngleSliderTrack>\n <AngleSliderRange />\n </AngleSliderTrack>\n <AngleSliderThumb />\n <AngleSliderValue />\n </AngleSlider>\n </div>\n );\n}\n",
"content": "\"use client\";\r\n\r\nimport { RotateCcwIcon, ShuffleIcon } from \"lucide-react\";\r\nimport { animate } from \"motion/react\";\r\nimport * as React from \"react\";\r\nimport { Button } from \"@/components/ui/button\";\r\nimport {\r\n AngleSlider,\r\n AngleSliderRange,\r\n AngleSliderThumb,\r\n AngleSliderTrack,\r\n AngleSliderValue,\r\n} from \"@/registry/bases/radix/ui/angle-slider\";\r\n\r\nexport default function AngleSliderControlledDemo() {\r\n const [value, setValue] = React.useState([180]);\r\n const animationRef = React.useRef<ReturnType<typeof animate> | null>(null);\r\n\r\n const animateToValue = React.useCallback(\r\n (targetValue: number) => {\r\n if (animationRef.current) {\r\n animationRef.current.stop();\r\n }\r\n\r\n const currentValue = value[0] ?? 0;\r\n\r\n let diff = targetValue - currentValue;\r\n if (diff > 180) {\r\n diff -= 360;\r\n } else if (diff < -180) {\r\n diff += 360;\r\n }\r\n\r\n animationRef.current = animate(0, diff, {\r\n duration: 0.4,\r\n ease: [0.25, 0.46, 0.45, 0.94],\r\n onUpdate: (progress: number) => {\r\n const animatedValue = currentValue + progress;\r\n const normalizedValue = Math.round(\r\n ((animatedValue % 360) + 360) % 360,\r\n );\r\n setValue([normalizedValue]);\r\n },\r\n onComplete: () => {\r\n setValue([targetValue]);\r\n animationRef.current = null;\r\n },\r\n });\r\n },\r\n [value],\r\n );\r\n\r\n const onReset = React.useCallback(() => {\r\n animateToValue(0);\r\n }, [animateToValue]);\r\n\r\n const onRandomize = React.useCallback(() => {\r\n animateToValue(Math.floor(Math.random() * 360));\r\n }, [animateToValue]);\r\n\r\n React.useEffect(() => {\r\n return () => {\r\n if (animationRef.current) {\r\n animationRef.current.stop();\r\n }\r\n };\r\n }, []);\r\n\r\n return (\r\n <div className=\"flex flex-col gap-4\">\r\n <div className=\"flex items-center gap-3\">\r\n <Button variant=\"outline\" size=\"sm\" onClick={onReset}>\r\n <RotateCcwIcon />\r\n Reset\r\n </Button>\r\n <Button size=\"sm\" onClick={onRandomize}>\r\n <ShuffleIcon />\r\n Randomize\r\n </Button>\r\n </div>\r\n <AngleSlider\r\n value={value}\r\n onValueChange={setValue}\r\n max={360}\r\n min={0}\r\n step={1}\r\n size={80}\r\n >\r\n <AngleSliderTrack>\r\n <AngleSliderRange />\r\n </AngleSliderTrack>\r\n <AngleSliderThumb />\r\n <AngleSliderValue />\r\n </AngleSlider>\r\n </div>\r\n );\r\n}\r\n",
"type": "registry:example",
"target": ""
}
Expand Down
Loading
Loading