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, both with a new drawing:
CoPilotIcon— lucideSparklesin place of the in-house solid sparkle.FilterIcon— 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 |
ChatPanel minimized bubble | in-house CoPilotIcon | CoPilotIcon (lucide Sparkles) | A solid sparkle pair becomes a stroked sparkle |
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,
PanelLeftIcon and CoPilotIcon. All three were in-house SVGs before. Radix
MagicWandIcon is the nearest stand-in for CoPilotIcon if you want one.
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 applies to any runtime icon override.
Next
See Icons for the full set, the override API, and what each key draws.