Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
bc2b4e8
feat(theme): element-mounted ThemePreview with radius, scaling and pa…
rohanchkrabrty Sep 2, 2026
6d56921
fix: comments
rohanchkrabrty Sep 17, 2026
a3c766b
fix(theme): address review feedback on ThemePreview
rohanchkrabrty Sep 17, 2026
00b59b6
Merge remote-tracking branch 'origin/main' into worktree-theme-revamp
rohanchkrabrty Sep 17, 2026
62cb7a9
fix(theme): Switch and Slider follow the theme radius
rohanchkrabrty Sep 17, 2026
60c636f
docs(theme): tighten the ThemePreview page and add nesting and panel …
rohanchkrabrty Sep 17, 2026
6313b57
fix(theme): scaling, radius and panel fixes from the live test pass
rohanchkrabrty Sep 18, 2026
8035bfe
docs(theme): make the panel background demos show translucency
rohanchkrabrty Sep 18, 2026
619fb91
fix(theme): match Radix Themes' translucent panel
rohanchkrabrty Sep 18, 2026
bb543e4
refactor(docs): mount the site on ThemePreview
rohanchkrabrty Sep 18, 2026
1f6dad0
docs(theme): fold ThemePreview into the theme overview
rohanchkrabrty Sep 18, 2026
3e10f48
docs(theme): soften the panel backdrop and widen the playground
rohanchkrabrty Sep 18, 2026
da96abe
docs(theme): drop the decorative backdrops from the panel demos
rohanchkrabrty Sep 18, 2026
69b5179
fix(theme): soften the panel blur to 40px
rohanchkrabrty Sep 18, 2026
719fdf4
fix(theme): land a whole appearance switch on one clock
rohanchkrabrty Sep 18, 2026
89b7ac8
fix(theme): restore the scrim behind portalled overlays
rohanchkrabrty Sep 18, 2026
f5b3a70
fix(docs): run react-live demos that declare a helper in noInline mode
rohanchkrabrty Sep 18, 2026
229a681
refactor(theme)!: remove the legacy Theme provider, rename ThemePrevi…
rohanchkrabrty Sep 18, 2026
5a4dfc2
fix(theme): skip the appearance crossfade for a scoped theme
rohanchkrabrty Sep 19, 2026
4b251a3
refactor(theme)!: make radius a cva variant, trim the public theme ex…
rohanchkrabrty Sep 19, 2026
5cf591d
refactor(hooks): move useScaling out of data-view
rohanchkrabrty Sep 19, 2026
0c78116
build(raystack): emit style-no-fonts.css from rollup, not a post-buil…
rohanchkrabrty Sep 19, 2026
3a223aa
docs: trim the comments this branch touched
rohanchkrabrty Sep 19, 2026
e47f243
Merge branch 'main' into worktree-theme-revamp
ravisuhag Sep 20, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions .agents/skills/apsara/SKILL.md
Original file line number Diff line number Diff line change
@@ -1,22 +1,22 @@
---
name: apsara
description: Helps consume the @raystack/apsara React component library correctly in an application. Use when installing or setting up Apsara, building UI with Apsara components (Button, Dialog, Select, Menu, DataTable, Form, Tabs, Sidebar, Toast, etc.), theming (light/dark, accent/gray colors, modern/traditional style), styling with design tokens, or troubleshooting Apsara component behavior. Covers install, the Theme provider, the `--rs-*` token system, compound-component composition, and common pitfalls.
description: Helps consume the @raystack/apsara React component library correctly in an application. Use when installing or setting up Apsara, building UI with Apsara components (Button, Dialog, Select, Menu, DataTable, Form, Tabs, Sidebar, Toast, etc.), theming (light/dark, accent/gray colors, radius, scaling), styling with design tokens, or troubleshooting Apsara component behavior. Covers install, the `Theme` component, the `--rs-*` token system, compound-component composition, and common pitfalls.
license: ISC
metadata:
author: raystack
---

# Apsara

Apsara (`@raystack/apsara`) is an open-source React component library built on [Base UI](https://base-ui.com/) primitives. It targets enterprise, data-dense interfaces (data tables, navigation shells, forms, overlays) and ships ~70 accessible, typed components. Styling is **vanilla CSS driven by `--rs-*` design tokens** that react to `data-*` attributes set by the theme provider — there is no Tailwind, no runtime CSS-in-JS, and no per-component install step.
Apsara (`@raystack/apsara`) is an open-source React component library built on [Base UI](https://base-ui.com/) primitives. It targets enterprise, data-dense interfaces (data tables, navigation shells, forms, overlays) and ships ~70 accessible, typed components. Styling is **vanilla CSS driven by `--rs-*` design tokens** that react to `data-*` attributes set by the `Theme` component — there is no Tailwind, no runtime CSS-in-JS, and no per-component install step.

## What this skill is for

Use this skill to help a **consumer** of the published package:

- install and wire up Apsara in a new or existing app (Next.js, Vite, etc.)
- pick and correctly compose Apsara components for a UI task
- theme the app (light/dark, accent/gray color, modern/traditional style)
- theme the app (light/dark, accent/gray color, radius, scaling, panel background, reduced motion)
- style and customize components using design tokens and `data-*` attributes
- avoid common composition and SSR pitfalls

Expand All @@ -36,7 +36,7 @@ When you need a prop you are unsure about, fetch the component's `.mdx` page rat
## Core facts (always true)

1. **One package, one CSS import.** `npm install @raystack/apsara`, then `import "@raystack/apsara/style.css"` once at the app root. That stylesheet contains every component's styles and all tokens.
2. **Wrap the app in `<Theme>`.** Theming, dark mode, and the no-flash hydration script all come from the `Theme` provider. Without it, `data-theme`/token resolution will not work.
2. **Wrap the app in `<Theme>`.** Every `--rs-*` token is declared on the element `Theme` renders, so anything outside it has no tokens at all. Dark mode and the no-flash script come from it too.
3. **Components are compound, dot-notation.** Apsara exports a single name per component and hangs sub-parts off it: `Dialog.Content`, `Select.Trigger`, `Menu.Item`, `Tabs.Tab`. It does **not** export flat names like `DialogContent`. (Contrast with shadcn/Radix and coss.)
4. **Style with tokens, never hard-coded values.** Use `--rs-*` custom properties (`var(--rs-color-foreground-base-primary)`, `var(--rs-space-5)`) so styling follows the active theme.
5. **Built on Base UI.** Components expose Base UI `data-*` state attributes (`data-open`, `data-disabled`, `data-starting-style`, …) for state-driven CSS, and trigger-based overlays follow Base UI composition.
Expand All @@ -62,7 +62,7 @@ When you need a prop you are unsure about, fetch the component's `.mdx` page rat
## References (read on demand)

- `references/setup.md` — install, CSS import, `<Theme>` wiring for Next.js App Router & Vite, icons & hooks subpath exports
- `references/theming.md` — full `Theme` / `ThemeProvider` API, `useTheme`, dark mode, accent/gray/style options, scoped (nested) themes, `ThemeSwitcher`
- `references/theming.md` — full `Theme` API, the seven settings, `useTheme`, dark mode, persistence, scoped (nested) themes, `ThemeSwitcher`
- `references/tokens.md` — **complete `--rs-*` token reference**: semantic colors, color scales, spacing, radius, typography, effects, theme `data-*` attributes
- `references/styling.md` — customizing components via `className`, `style`, `data-*`, CSS Modules, and CVA
- `references/composition.md` — compound dot-notation pattern, Base UI trigger/overlay composition, `render` prop, and per-component composition gotchas
Expand Down
4 changes: 2 additions & 2 deletions .agents/skills/apsara/references/components.md
Original file line number Diff line number Diff line change
Expand Up @@ -129,9 +129,9 @@ All components import from the root: `import { Button, Dialog } from "@raystack/

| Export | Purpose |
|---|---|
| `Theme` (alias `ThemeProvider`, deprecated) | Theme provider — wrap the app. See `theming.md`. |
| `Theme` | Theme element — wrap the app, nest to scope. See `theming.md`. |
| `ThemeSwitcher` | Prebuilt light/dark toggle button |
| `useTheme` | Hook to read/set theme (also at `@raystack/apsara/hooks`) |
| `useTheme` | Hook to read/set the theme; throws outside a `Theme` |

## Notes

Expand Down
31 changes: 16 additions & 15 deletions .agents/skills/apsara/references/setup.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,27 +35,27 @@ import "@raystack/apsara/style.css";

## 3. Wrap the app in `<Theme>`

The `Theme` provider applies `data-theme` / `data-style` / `data-accent-color` / `data-gray-color` to the document and injects a small inline script that sets them **before paint** to avoid a flash of the wrong theme. Tokens only resolve correctly inside it.
`Theme` renders an element carrying `data-theme` / `data-accent-color` / `data-gray-color` / `data-radius` / `data-scaling` / `data-panel-background` / `data-reduced-motion`, and every `--rs-*` token is declared under those attributes. **Tokens only resolve inside it**, so it has to wrap anything that reads them.

```tsx
import { Theme } from "@raystack/apsara";

function App() {
return (
<Theme defaultTheme="system">
<Theme persistKey="app-theme" defaultValue={{ appearance: "system" }}>
<YourApp />
</Theme>
);
}
```

`defaultTheme` accepts `"light"`, `"dark"`, or `"system"` (follows OS preference). See `theming.md` for the full prop list (accent color, gray color, style variant, storage key, forced theme, etc.).
`defaultValue` seeds the seven settings; `appearance` accepts `"light"`, `"dark"` or `"system"` (follows OS preference). `persistKey` turns on persistence and names its storage entry — without it, settings live in memory only. See `theming.md` for the full settings and prop list.

## Framework wiring

### Next.js (App Router)

Put the CSS import and provider in the root layout. `suppressHydrationWarning` on `<html>` is **required** because the no-flash script mutates `<html>` attributes before React hydrates.
Put the CSS import and the theme in the root layout. `suppressHydrationWarning` on `<html>` is **not** needed: nothing is written to `<html>`, and `Theme` marks its own element.

```tsx
// app/layout.tsx
Expand All @@ -64,17 +64,18 @@ import "@raystack/apsara/style.css";

export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en" suppressHydrationWarning>
<html lang="en">
<body>
<Theme defaultTheme="system">{children}</Theme>
<Theme persistKey="app-theme">{children}</Theme>
</body>
</html>
);
}
```

Notes for the App Router:
- The CSS import and `Theme` can live in a server component (layout); `Theme` itself is a client component (`"use client"`) and handles that boundary internally.
- The CSS import and `Theme` can live in a server component (layout); `Theme` itself is a client component (`"use client"`) and handles that boundary internally. It server-renders its attributes on the first byte.
- Passing `icons` requires a client component, because an override map is an object of functions. Move the theme into a `providers.tsx` marked `'use client'` in that case.
- Interactive Apsara components are client components — render them within client boundaries as usual.

### Vite / CRA / SPA
Expand All @@ -91,7 +92,7 @@ import App from "./App";

ReactDOM.createRoot(document.getElementById("root")!).render(
<React.StrictMode>
<Theme defaultTheme="system">
<Theme persistKey="app-theme">
<App />
</Theme>
</React.StrictMode>
Expand All @@ -102,16 +103,16 @@ ReactDOM.createRoot(document.getElementById("root")!).render(

| Import | Contents |
|---|---|
| `@raystack/apsara` | All components, the `Theme` provider, `toastManager`/`useToastManager`, type exports |
| `@raystack/apsara/icons` | Icon set (re-exports + Apsara icons) |
| `@raystack/apsara/hooks` | Utility hooks (e.g. `useTheme`) |
| `@raystack/apsara` | All components, `Theme`/`useTheme`/`ThemeSwitcher`, `toastManager`/`useToastManager`, type exports |
| `@raystack/apsara/icons` | The 31 icons Apsara's components draw, plus `createIcon` |
| `@raystack/apsara/hooks` | Utility hooks (`useCopyToClipboard`, `useDebouncedState`, `useIsomorphicLayoutEffect`, `useMouse`) |
| `@raystack/apsara/style.css` | The full stylesheet (required) |
| `@raystack/apsara/normalize.css` | Optional CSS reset |

```tsx
import { Button } from "@raystack/apsara";
import { MagnifyingGlassIcon, Cross2Icon } from "@raystack/apsara/icons";
import { useTheme } from "@raystack/apsara/hooks"; // also re-exported from the root
import { useCopyToClipboard } from "@raystack/apsara/hooks";
```

> `@raystack/apsara/v1` is an alias of the root entry kept for compatibility; new code should import from `@raystack/apsara`.
Expand All @@ -137,6 +138,6 @@ export function Example() {
## Setup troubleshooting

- **Components render unstyled / tokens are blank** → `style.css` isn't imported, or it's imported after a CSS reset that overrides it. Import it once at the root.
- **Colors don't change with light/dark, or `var(--rs-color-*)` resolves to nothing** → the tree isn't wrapped in `<Theme>`, so `data-theme` is never set on the document.
- **Theme flashes wrong on first paint (Next.js)** → missing `suppressHydrationWarning` on `<html>`, or `Theme` isn't high enough in the tree.
- **Hydration mismatch warnings around theme** → expected without `suppressHydrationWarning`; add it to `<html>`.
- **Colors don't change with light/dark, or `var(--rs-color-*)` resolves to nothing** → the element isn't inside `<Theme>`. Tokens are declared on the theme element, not `<html>`, so anything outside it — including a hand-rolled portal into `document.body` — has no colors at all.
- **`useTheme` throws** → it is being called outside a `<Theme>`. That is deliberate; move the caller inside.
- **Theme flashes wrong on first paint** → `Theme` has no `persistKey`, so it emits no pre-hydration script, or it isn't high enough in the tree.
12 changes: 6 additions & 6 deletions .agents/skills/apsara/references/styling.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,22 +47,22 @@ Common attributes: `data-open`, `data-closed`, `data-active`, `data-disabled`, `

## Theme-conditional styling

`<Theme>` sets `data-theme` / `data-style` / `data-accent-color` / `data-gray-color` on the root (and scope wrappers). Target them for theme-specific overrides:
`<Theme>` writes one attribute per setting — `data-theme`, `data-accent-color`, `data-gray-color`, `data-radius`, `data-scaling`, `data-panel-background`, `data-reduced-motion` — onto the element it renders. Match an ancestor rather than `<html>`, so a nested scope styles correctly too:

```css
[data-theme="dark"] .custom-card { border-color: var(--rs-color-border-base-tertiary); }
[data-style="traditional"] .heading { font-family: var(--rs-font-title); }
[data-accent-color="orange"] .heading { color: var(--rs-color-foreground-accent-primary); }
```

## Overriding tokens (custom palette / sizing)

To re-skin globally or per-scope, redefine **semantic** tokens under a selector. Prefer semantic tokens over raw scale steps so the override stays theme-correct.

Every `--rs-*` declaration is wrapped in `:where()` and every theme element carries the stable `rs-theme` class, so one class selector wins without `!important`.

```css
/* Make the danger emphasis fill a custom red across the app */
:root,
[data-theme="light"],
[data-theme="dark"] {
/* Make the accent emphasis fill a custom color across the app */
.rs-theme {
--rs-color-background-accent-emphasis: var(--rs-color-viz-iris-9);
}
```
Expand Down
Loading
Loading