Migrating to lucide icons
What changes when Apsara replaces @radix-ui/react-icons with lucide, and how to keep the old appearance.Apsara used to draw its icons with @radix-ui/react-icons.
It now draws them with lucide, behind stable keys you can
replace one at a time.
This is a breaking release: some icons look different, lucide-react is a new
peer dependency, and the names @raystack/apsara/icons exports have changed.
1. Install the peer dependency
1npm install lucide-react
The range is wide — >=0.500.0 <1.0.0 — so your app picks the version. If a
lucide release changes a drawing you care about, replace that one icon (step 5)
rather than pinning the whole library.
2. Rename what you imported from @raystack/apsara/icons
That path used to export raw in-house SVG components. It now exports the 31 icons Apsara's components draw, as replaceable icon components. Twelve of the old names are gone.
| Removed name | Use instead | Appearance |
|---|---|---|
BellIcon | lucide Bell | Same glyph |
BellSlashIcon | lucide BellOff | Similar |
BuildingsFilledIcon | lucide Building2 | Solid becomes stroke |
CheckCircleFilledIcon | lucide CircleCheck | Solid becomes stroke |
CoinIcon | lucide Coins | Similar |
CoinColoredIcon | lucide Coins | Loses its colour |
CrossCircleFilledIcon | lucide CircleX | Solid becomes stroke |
OrganizationIcon | lucide Building2 | Similar |
ResetIcon | lucide RotateCcw | Similar |
ShoppingBagFilledIcon | lucide ShoppingBag | Solid becomes stroke |
SidebarIcon | PanelLeftIcon, or lucide PanelLeft | Similar |
TriangleRightIcon | ChevronRightIcon | Solid triangle becomes a chevron |
Two names survive:
CoPilotIcon— unchanged. lucide has no equivalent, so it is still the in-house SVG.FilterIcon— same name, new drawing: lucideListFilterin place of the in-house solid funnel.
A raw lucide component draws 24×24 at strokeWidth={2}, so set
size={16} strokeWidth={1.5} at the call site to match the Apsara icons beside
it — or wrap it once with createIcon, which applies those for you:
1// src/icons.ts2import { createIcon } from '@raystack/apsara/icons';3import { Bell } from 'lucide-react';45export const BellIcon = createIcon('BellIcon', Bell);
3. Check the icons that changed shape
These are inside Apsara's own components, so they change without you touching a call site. Everything else is the same glyph in a different drawing style.
| Where | Before (radix) | After | What changed |
|---|---|---|---|
Sidebar collapse | ViewVerticalIcon | PanelLeftIcon | A different glyph |
Sidebar group toggle | TriangleDownIcon | ChevronDownIcon | A solid triangle becomes a chevron |
Menu and ContextMenu submenu marker | in-house TriangleRightIcon | ChevronRightIcon | A solid triangle becomes a chevron |
ChatPanel expand | SizeIcon | ExpandIcon | A different glyph |
ChatPanel minimize | MinusIcon | ShrinkIcon | A dash becomes the matched pair of ExpandIcon |
PromptInput stop | StopIcon | StopIcon (lucide Square) | Solid becomes stroke |
DataTable sort ascending | TextAlignTopIcon | SortAscendingIcon | A different glyph |
DataTable and DataView sort descending | TextAlignBottomIcon | SortDescendingIcon | A different glyph |
DataTable and DataView display settings | MixerHorizontalIcon | DisplayIcon | Similar |
DataTable and DataView filters | in-house FilterIcon | FilterIcon (lucide ListFilter) | A solid funnel becomes filter lines |
Two more are worth a look, though the glyph is nearly the same:
DatePickerandRangePickerdrawCalendarIcon, which is lucideCalendarDays, so the glyph has day marks inside it.Search's clear button andToast's error status drawCircleXin place of radixCrossCircledIcon.
4. Expect a 1px size change in some places
Every Apsara icon renders at 16×16 with strokeWidth={1.5}, which draws the
1px stroke of the design because lucide's viewBox is 24 units wide. The radix
icons were intrinsically 15×15.
- A call site that set no size grows from 15px to 16px.
- A call site that set a CSS class is unaffected — CSS beats an SVG presentation attribute.
- A call site that set
width/heightexplicitly is unaffected — your props are applied after Apsara's base values.
To change the size or the stroke of every icon at once, use the props half of
<Theme icons>:
1<Theme icons={{ props: { width: 20, height: 20, strokeWidth: 1.25 } }}>
5. If you want the radix appearance back
Apsara ships no radix preset, so register the radix icons yourself at <Theme>.
Keep @radix-ui/react-icons in your own dependencies and copy this map:
1'use client';23import {4 ArrowDownIcon,5 ArrowUpIcon,6 CalendarIcon,7 CheckCircledIcon,8 CheckIcon,9 ChevronDownIcon,10 ChevronLeftIcon,11 ChevronRightIcon,12 CopyIcon,13 Cross1Icon,14 CrossCircledIcon,15 DotsHorizontalIcon,16 ExclamationTriangleIcon,17 FileTextIcon,18 InfoCircledIcon,19 MagnifyingGlassIcon,20 MinusIcon,21 MixerHorizontalIcon,22 MoonIcon,23 PlusIcon,24 SizeIcon,25 StopIcon,26 SunIcon,27 TableIcon,28 TextAlignBottomIcon,29 TextAlignTopIcon30} from '@radix-ui/react-icons';31import { Theme, type IconOverrides } from '@raystack/apsara';3233const radixIcons: IconOverrides = {34 ArrowDownIcon: ArrowDownIcon,35 ArrowUpIcon: ArrowUpIcon,36 CalendarIcon: CalendarIcon,37 CheckIcon: CheckIcon,38 ChevronDownIcon: ChevronDownIcon,39 ChevronLeftIcon: ChevronLeftIcon,40 ChevronRightIcon: ChevronRightIcon,41 ClearIcon: CrossCircledIcon,42 CopyIcon: CopyIcon,43 DisplayIcon: MixerHorizontalIcon,44 EllipsisIcon: DotsHorizontalIcon,45 ErrorIcon: CrossCircledIcon,46 ExpandIcon: SizeIcon,47 FileTextIcon: FileTextIcon,48 InfoIcon: InfoCircledIcon,49 MinusIcon: MinusIcon,50 MoonIcon: MoonIcon,51 PlusIcon: PlusIcon,52 SearchIcon: MagnifyingGlassIcon,53 ShrinkIcon: MinusIcon,54 SortAscendingIcon: TextAlignTopIcon,55 SortDescendingIcon: TextAlignBottomIcon,56 StopIcon: StopIcon,57 SuccessIcon: CheckCircledIcon,58 SunIcon: SunIcon,59 TableIcon: TableIcon,60 WarningIcon: ExclamationTriangleIcon,61 XIcon: Cross1Icon62};6364export function Providers({ children }: { children: React.ReactNode }) {65 return <Theme icons={{ components: radixIcons }}>{children}</Theme>;66}
Three keys are not in the map, because radix has no equivalent: FilterIcon and
PanelLeftIcon were in-house SVGs before, and CoPilotIcon still is.
You do not have to take the whole map. A partial map changes only the keys it names.
6. Register from a client component
An override map is an object of functions, and a function cannot cross the
boundary from a React Server Component to a Client Component. If your <Theme>
sits directly in a server layout today, move it into a providers.tsx file
marked 'use client', as shown above.
This constraint applies to any runtime icon override, not just this design.
Next
See Icons for the naming rules, the override API, and the gallery of the full set.