Getting Started
A quick tutorial to get you up and running with Apsara.
This guide walks you through installing Apsara and building your first component.
Prerequisites
Apsara requires:
- Node.js 22 or later
- React 19
React and React DOM are peer dependencies. Make sure your project has them installed:
1npm install react react-dom
Installation
Install the package using your preferred package manager:
1npm install @raystack/apsara
Try a preview build
Every pull request on GitHub gets a canary build, published automatically via pkg.pr.new. This lets you try out unreleased changes before they ship to npm. The PR will have a comment with an install command, for example:
1pnpm add https://pkg.pr.new/raystack/apsara/@raystack/apsara@<pr-number>
Setup
Three steps, in order. The styles and the provider are needed once, at the root of the app.
1. Import styles
Add the CSS import at the root of your application, before any component renders:
1import "@raystack/apsara/style.css";
This single stylesheet includes all component styles and CSS custom properties (tokens) for theming.
2. Add Theme
Wrap your application with Theme to enable theming support:
1import { Theme } from "@raystack/apsara";23function App() {4 return (5 <Theme persistKey="app-theme">6 <YourApp />7 </Theme>8 );9}
Tokens live on the element it renders, so anything that reads --rs-* has to be inside it. persistKey stores the user's choices; leave it off to keep them in memory. Seed any of the seven settings with defaultValue, for example defaultValue={{ appearance: "dark" }}.
3. Use components
Import and use components directly:
1import { Button, Flex, Text } from "@raystack/apsara";23function Example() {4 return (5 <Flex direction="column" gap={4}>6 <Text size="regular" weight="medium">Welcome to Apsara</Text>7 <Flex gap={3}>8 <Button variant="solid" color="accent">Primary Action</Button>9 <Button variant="outline">Secondary</Button>10 </Flex>11 </Flex>12 );13}
Framework setup
Where the two setup steps go depends on the framework.
Next.js (App Router)
Add the provider to your root layout:
1// app/layout.tsx2import { Theme } from "@raystack/apsara";3import "@raystack/apsara/style.css";45export default function RootLayout({ children }: { children: React.ReactNode }) {6 return (7 <html lang="en">8 <body>9 <Theme persistKey="app-theme">10 {children}11 </Theme>12 </body>13 </html>14 );15}
Settings are props, so the server renders them. An inline script patches in what it cannot know before first paint: stored values, and the OS answer for a system appearance. Nothing is written to <html>, so it needs no suppressHydrationWarning.
Vite
Add the provider to your main entry file:
1// main.tsx2import React from "react";3import ReactDOM from "react-dom/client";4import { Theme } from "@raystack/apsara";5import "@raystack/apsara/style.css";6import App from "./App";78ReactDOM.createRoot(document.getElementById("root")!).render(9 <React.StrictMode>10 <Theme persistKey="app-theme">11 <App />12 </Theme>13 </React.StrictMode>14);
Optional: normalize CSS
Apsara includes an optional normalize stylesheet that keeps rendering consistent across browsers while preserving useful defaults:
1import "@raystack/apsara/normalize.css";2import "@raystack/apsara/style.css";
Import it before the main stylesheet if you choose to use it.
Importing icons
Apsara exports a set of icons that can be imported separately:
1import { Button } from "@raystack/apsara";2import { SearchIcon, XIcon } from "@raystack/apsara/icons";34<Button leadingIcon={<SearchIcon />}>Search</Button>
Importing hooks
Utility hooks such as useCopyToClipboard, useDebouncedState, and useMouse are available from a dedicated export:
1import { Button } from "@raystack/apsara";2import { useCopyToClipboard } from "@raystack/apsara/hooks";34function CopyButton({ value }: { value: string }) {5 const { copy } = useCopyToClipboard();67 return <Button onClick={() => copy(value)}>Copy</Button>;8}
To read or change the active theme, use useTheme from the main entry:
1import { Button, useTheme } from "@raystack/apsara";23function ThemeToggle() {4 const { resolved, setValue } = useTheme();5 const isDark = resolved.appearance === "dark";67 return (8 <Button onClick={() => setValue({ appearance: isDark ? "light" : "dark" })}>9 Toggle theme10 </Button>11 );12}
Next steps
- Theme Overview: tokens, the seven settings, and nested scopes
- Button: start with a common component
- DataView: build data-rich interfaces