diff --git a/apps/www/src/app/examples/page.tsx b/apps/www/src/app/examples/page.tsx
index dc326dc30..b65134762 100644
--- a/apps/www/src/app/examples/page.tsx
+++ b/apps/www/src/app/examples/page.tsx
@@ -1,4 +1,17 @@
'use client';
+import {
+ ActivityLogIcon,
+ BarChartIcon,
+ DashboardIcon,
+ DotsHorizontalIcon,
+ FileTextIcon,
+ GearIcon,
+ HomeIcon,
+ MixerHorizontalIcon,
+ PersonIcon,
+ QuestionMarkCircledIcon,
+ BellIcon as RadixBellIcon
+} from '@radix-ui/react-icons';
import {
Amount,
Avatar,
@@ -30,12 +43,6 @@ import {
TextArea,
Tooltip
} from '@raystack/apsara';
-import {
- BellIcon,
- FilterIcon,
- OrganizationIcon,
- SidebarIcon
-} from '@raystack/apsara/icons';
import dayjs from 'dayjs';
import React, { useState } from 'react';
@@ -59,16 +66,16 @@ const Page = () => {
// Sample options data with icons
const selectOptions = [
- { value: 'dashboard', label: 'Dashboard', icon: },
- { value: 'analytics', label: 'Analytics', icon: },
- { value: 'settings', label: 'Settings', icon: },
- { value: 'profile', label: 'Profile', icon: }
+ { value: 'dashboard', label: 'Dashboard', icon: },
+ { value: 'analytics', label: 'Analytics', icon: },
+ { value: 'settings', label: 'Settings', icon: },
+ { value: 'profile', label: 'Profile', icon: }
];
const filterOptions = [
- { value: 'Option 1', label: 'Option 1', icon: },
- { value: 'Option 2', label: 'Option 2', icon: },
- { value: 'Option 3', label: 'Option 3', icon: }
+ { value: 'Option 1', label: 'Option 1', icon: },
+ { value: 'Option 2', label: 'Option 2', icon: },
+ { value: 'Option 3', label: 'Option 3', icon: }
];
return (
@@ -79,7 +86,7 @@ const Page = () => {
backgroundColor: 'var(--rs-color-background-base-primary)'
}}
>
-
+
{
onClick={() => console.log('Logo clicked')}
aria-label='Logo'
>
-
+
Raystack
@@ -96,31 +103,103 @@ const Page = () => {
- }>
+ }>
Dashboard
- }>
+ }>
Analytics
- }>
- Reports
-
- Activities
+ alert('Resources trailing icon clicked')}
+ aria-label='Resources group actions'
+ style={{
+ border: 0,
+ background: 'transparent',
+ color: 'inherit',
+ padding: 0,
+ display: 'inline-flex',
+ alignItems: 'center',
+ cursor: 'pointer'
+ }}
+ >
+
+
+ }
+ >
+ }>
+ Reports
+
+
+ }
+ >
+ Activities
+
+ console.log('Notifications clicked')}
+ leadingIcon={}
+ >
+ Notifications
+
+
-
- Settings
-
- Notifications
+ alert('Account trailing icon clicked')}
+ aria-label='Account group actions'
+ style={{
+ border: 0,
+ background: 'transparent',
+ color: 'inherit',
+ padding: 0,
+ display: 'inline-flex',
+ alignItems: 'center',
+ cursor: 'pointer'
+ }}
+ >
+
+
+ }
+ >
+ }>
+ Settings
+
+ }>
+ }>
+ Notifications
+
+ } disabled>
+ Billing
+
+
- Help & Support
-
- Preferences
+ }>
+ Help & Support
+
+
+ }>
+ Preferences
+
+ }>
+ Documentation
+
+
@@ -283,7 +362,7 @@ const Page = () => {
color: 'var(--rs-color-foreground-base-secondary)'
}}
>
-
+
25%
@@ -298,7 +377,7 @@ const Page = () => {
color: 'var(--rs-color-foreground-base-secondary)'
}}
>
-
+
25%
@@ -313,7 +392,7 @@ const Page = () => {
color: 'var(--rs-color-foreground-base-secondary)'
}}
>
-
+
25%
@@ -350,7 +429,9 @@ const Page = () => {
color: 'var(--rs-color-foreground-base-secondary)'
}}
>
-
+
Sun
@@ -369,7 +450,9 @@ const Page = () => {
color: 'var(--rs-color-foreground-base-secondary)'
}}
>
-
+
15th
@@ -388,7 +471,9 @@ const Page = () => {
color: 'var(--rs-color-foreground-base-secondary)'
}}
>
-
+
Today
@@ -1362,7 +1447,7 @@ const Page = () => {
}
+ icon={}
heading='KYC required for image orders'
subHeading='Please contact your organization owner to complete the KYC process for the image orders. You can also contact support@raystack.io for assistance.'
primaryAction={
@@ -1559,7 +1644,7 @@ const Page = () => {
@@ -1580,7 +1665,7 @@ const Page = () => {
}
+ leadingIcon={}
width='100%'
/>
@@ -1717,7 +1802,7 @@ const Page = () => {
@@ -1749,7 +1834,7 @@ const Page = () => {
}
+ leadingIcon={}
width='100%'
/>
@@ -2191,7 +2276,7 @@ const Page = () => {
}
+ icon={}
heading='Zero state'
variant='empty2'
subHeading='Get started by creating your first user. Filter bar and search are hidden in zero state.'
@@ -2199,7 +2284,7 @@ const Page = () => {
}
emptyState={
}
+ icon={}
heading='Empty state'
variant='empty1'
subHeading="We couldn't find any matches for that keyword or filter."
@@ -2271,7 +2356,7 @@ const Page = () => {
}
+ icon={}
heading='zero state'
variant='empty2'
subHeading='Get started by creating your first user.'
@@ -2279,7 +2364,7 @@ const Page = () => {
}
emptyState={
}
+ icon={}
heading='empty state'
variant='empty1'
subHeading="We couldn't find any matches for that filter. Try adjusting your filters or search query. Filter bar remains visible so you can modify filters."
@@ -2346,7 +2431,7 @@ const Page = () => {
}
+ icon={}
heading='zero state'
variant='empty2'
subHeading='Get started by creating your first user.'
@@ -2354,7 +2439,7 @@ const Page = () => {
}
emptyState={
}
+ icon={}
heading='empty state'
variant='empty1'
subHeading="We couldn't find any matches for that search. Try a different search term. Filter bar stays hidden when only search is applied."
@@ -2407,7 +2492,7 @@ const Page = () => {
align='center'
style={{ padding: '40px' }}
>
- {
}
emptyState={
}
+ icon={}
heading='empty state'
variant='empty1'
subHeading='Try adjusting your filters or search query.'
@@ -2477,7 +2562,7 @@ const Page = () => {
}
+ icon={}
heading='zero state'
variant='empty2'
subHeading='Search is enabled even in zero state. Start typing to see empty state. Filter bar will only appear when filters are applied.'
@@ -2485,7 +2570,7 @@ const Page = () => {
}
emptyState={
}
+ icon={}
heading='empty state'
variant='empty1'
subHeading='Search applied but no results. Filter bar stays hidden when only search is used.'
@@ -2551,7 +2636,7 @@ const Page = () => {
}
+ icon={}
heading='zero state'
variant='empty2'
subHeading='Get started by creating your first user.'
@@ -2559,7 +2644,7 @@ const Page = () => {
}
emptyState={
}
+ icon={}
heading='empty state'
variant='empty1'
subHeading="We couldn't find any matches for that keyword or filter."
diff --git a/apps/www/src/content/docs/components/sidebar/demo.ts b/apps/www/src/content/docs/components/sidebar/demo.ts
index cfb8dc34e..8473ab3b1 100644
--- a/apps/www/src/content/docs/components/sidebar/demo.ts
+++ b/apps/www/src/content/docs/components/sidebar/demo.ts
@@ -27,6 +27,9 @@ export const preview = {
+ }>
+ Overview
+
} active>
Dashboard
@@ -46,11 +49,6 @@ export const preview = {
Activities
-
- }>
- Help
-
-
}>
@@ -76,6 +74,7 @@ export const positionDemo = {
+ }>Overview
} active>Dashboard
}>Analytics
@@ -100,6 +99,7 @@ export const positionDemo = {
+ }>Overview
} active>Dashboard
}>Analytics
@@ -114,6 +114,75 @@ export const positionDemo = {
]
};
+export const variantDemo = {
+ type: 'code',
+ tabs: [
+ {
+ name: 'Plain',
+ code: sidebarLayout(`
+
+
+
+
+
+
+ Apsara
+
+
+
+ }>Overview
+
+ } active>Dashboard
+ }>Analytics
+
+
+ `)
+ },
+ {
+ name: 'Floating',
+ code: sidebarLayout(`
+
+
+
+
+
+
+ Apsara
+
+
+
+ }>Overview
+
+ } active>Dashboard
+ }>Analytics
+
+
+ `)
+ },
+ {
+ name: 'Inset',
+ code: sidebarLayout(`
+
+
+
+
+
+
+ Apsara
+
+
+
+ }>Overview
+
+ } active>Dashboard
+ }>Analytics
+
+
+ `)
+ }
+ ]
+};
+
export const stateDemo = {
type: 'code',
tabs: [
@@ -129,6 +198,7 @@ export const stateDemo = {
+ }>Overview
} active>Dashboard
}>Analytics
@@ -152,6 +222,7 @@ export const stateDemo = {
+ }>Overview
} active>Dashboard
}>Analytics
@@ -175,6 +246,7 @@ export const stateDemo = {
+ }>Overview
} active>Dashboard
}>Analytics
@@ -198,6 +270,7 @@ export const stateDemo = {
+ }>Overview
} active>Dashboard
}>Analytics
@@ -227,6 +300,7 @@ export const tooltipDemo = {
+ }>Overview
} active>Dashboard
}>Analytics
@@ -251,6 +325,7 @@ export const collapsibleDemo = {
+ }>Overview
} active>Dashboard
}>Analytics
@@ -271,10 +346,91 @@ export const hideTooltipDemo = {
+ }>Overview
} active>Dashboard
}>Settings
+ }>
+ Help
+
+
+ `)
+};
+
+export const collapsibleGroupDemo = {
+ type: 'code',
+ code: sidebarLayout(`
+
+
+
+
+
+ Apsara
+
+
+
+ }>
+ Overview
+
+
+ }>
+ Reports
+
+ }>
+ Activities
+
+
+ }>
+ }>
+ Settings
+
+
+
+ `)
+};
+
+export const moreDemo = {
+ type: 'code',
+ code: sidebarLayout(`
+
+
+
+
+
+ Apsara
+
+
+
+ } active>
+ Dashboard
+
+ }>
+ Analytics
+
+
+ }>
+ Reports
+
+
+ }>
+ Activities
+
+ } disabled>
+ Notifications
+
+
+
+
+
+ }>
+ Preferences
+
+ }>
+ Documentation
+
+
+
`)
};
diff --git a/apps/www/src/content/docs/components/sidebar/index.mdx b/apps/www/src/content/docs/components/sidebar/index.mdx
index 636e4802d..584698a9b 100644
--- a/apps/www/src/content/docs/components/sidebar/index.mdx
+++ b/apps/www/src/content/docs/components/sidebar/index.mdx
@@ -7,10 +7,13 @@ source: packages/raystack/components/sidebar
import {
preview,
positionDemo,
+ variantDemo,
+ collapsibleGroupDemo,
stateDemo,
tooltipDemo,
collapsibleDemo,
- hideTooltipDemo
+ hideTooltipDemo,
+ moreDemo
} from "./demo.ts";
@@ -27,6 +30,9 @@ import { Sidebar } from "@raystack/apsara";
Item
+
+ Hidden item
+
@@ -55,7 +61,7 @@ The main section wraps navigation groups and items. It accepts all `div` props a
### Item
-*Note: `leadingIcon` is optional and will show a fallback avatar only in collapsed state. You can pass `<>>` to render truly nothing. Use the `as` prop to render as a custom element (e.g. a router `Link`).*
+*Note: `leadingIcon` is optional and will show a fallback avatar only in collapsed state. You can pass `<>>` to render truly nothing. Use the `render` prop to render as a custom element (e.g. a router `Link`).*
@@ -63,6 +69,14 @@ The main section wraps navigation groups and items. It accepts all `div` props a
The footer section is a container that accepts all `div` props. It's commonly used for secondary links (e.g. Help, Preferences) and stays at the bottom of the sidebar.
+### More
+
+Renders a sidebar row that opens an Apsara menu with additional `Sidebar.Item` entries. It can be used inside `Sidebar.Group` or directly under `Sidebar.Main` / `Sidebar.Footer`.
+
+*Note: if `leadingIcon` is not provided, the trigger uses a default dots icon. In collapsed state, it follows the same item tooltip behavior and respects `hideCollapsedItemTooltip`.*
+
+
+
## Examples
### Position
@@ -71,6 +85,16 @@ The Sidebar can be positioned on either the left or right side of the screen.
+### Variants
+
+Use `variant` to switch the Sidebar surface style:
+
+- `plain` (default): regular surface with side border
+- `floating`: lifted surface with shadow
+- `inset`: transparent surface without border or shadow
+
+
+
### State
The Sidebar supports expanded and collapsed states with smooth transitions.
@@ -99,6 +123,18 @@ Set `hideCollapsedItemTooltip` to disable tooltips on navigation items when the
+### Collapsible Group
+
+Enable `collapsible` on `Sidebar.Group` to make section items collapsible. You can also pass `trailingIcon` for section-level actions.
+
+
+
+### More
+
+Use `Sidebar.More` when you want to keep a section compact and move secondary items into a menu.
+
+
+
## Accessibility
The Sidebar implements the following accessibility features:
diff --git a/apps/www/src/content/docs/components/sidebar/props.ts b/apps/www/src/content/docs/components/sidebar/props.ts
index ccf03e15a..b9fedd4fc 100644
--- a/apps/www/src/content/docs/components/sidebar/props.ts
+++ b/apps/www/src/content/docs/components/sidebar/props.ts
@@ -22,6 +22,11 @@ export interface SidebarRootProps {
*/
position?: 'left' | 'right';
+ /** Visual style variant of the Sidebar.
+ * @default "plain"
+ */
+ variant?: 'plain' | 'floating' | 'inset';
+
/** Hide tooltips on sidebar items when sidebar is collapsed.
* @default false
*/
@@ -37,6 +42,11 @@ export interface SidebarGroupProps {
/** String for the group title. */
label: string;
+ /** Makes group items collapsible.
+ * @default false
+ */
+ collapsible?: boolean;
+
/** Optional ReactNode for group icon. */
leadingIcon?: ReactNode;
@@ -71,7 +81,7 @@ export interface SidebarItemProps {
*
* @default ""
*/
- as?: ReactElement;
+ render?: ReactElement;
/** Optional class names for customizing parts of the item. */
classNames?: {
@@ -83,3 +93,26 @@ export interface SidebarItemProps {
text?: string;
};
}
+
+export interface SidebarMoreProps {
+ /** String for the more trigger label. */
+ label?: string;
+
+ /** Optional ReactNode for the trigger icon. */
+ leadingIcon?: ReactNode;
+
+ /** Sidebar items rendered inside the menu content. */
+ children?: ReactNode;
+
+ /** Optional class names for customizing parts of the more trigger/menu. */
+ classNames?: {
+ /** Class name for the trigger root element. */
+ root?: string;
+ /** Class name for the leading icon container. */
+ leadingIcon?: string;
+ /** Class name for the text element. */
+ text?: string;
+ /** Class name for menu content container. */
+ menuContent?: string;
+ };
+}
diff --git a/docs/V1-migration.md b/docs/V1-migration.md
index 38ef61c69..ea9ff3000 100644
--- a/docs/V1-migration.md
+++ b/docs/V1-migration.md
@@ -1080,40 +1080,62 @@ Key changes:
1. **`disabled` prop replaced by `collapsible={false}`:**
```tsx
-// Before — disabled prevents toggling but still shows trigger
+// Before
-// After — collapsible={false} hides the resize handle entirely
+// After
```
-2. **`asChild` removed from Root** -- always renders `