Styling
How to style and customize Apsara components in your application.Apsara uses vanilla CSS with CSS custom properties (tokens) for all styling. There is no runtime CSS-in-JS — styles are static, scoped, and predictable. This guide covers the recommended ways to customize components and build your own styled elements.
Using Design Tokens
Design tokens are CSS custom properties that adapt automatically to the active theme. Always use tokens instead of hard-coded values — this ensures your UI stays consistent and responds to light/dark mode changes.
1.card {2 color: var(--rs-color-foreground-base-primary);3 background: var(--rs-color-background-base-secondary);4 border: 1px solid var(--rs-color-border-base-primary);5 padding: var(--rs-space-5);6 border-radius: var(--rs-radius-3);7 box-shadow: var(--rs-shadow-feather);8 font-size: var(--rs-font-size-small);9}
See the Theme section for the complete token reference.
Customizing Components
With className
Every Apsara component accepts a className prop. Use it alongside tokens to add custom styles:
1import { Button } from "@raystack/apsara";23<Button className="my-button" variant="outline">4 Custom styled5</Button>
1.my-button {2 min-width: 200px;3 border-radius: var(--rs-radius-5);4}
With style Prop
For one-off adjustments, use the inline style prop:
1<Flex gap="4" style={{ maxWidth: 600, margin: "0 auto" }}>2 <Button style={{ flex: 1 }}>Full width</Button>3</Flex>
With Data Attributes
Apsara components built on Base UI expose data attributes that reflect component state. Use these for state-driven styling without JavaScript:
1/* Style a sidebar based on open/closed state */2.my-sidebar[data-open] {3 width: 240px;4}56.my-sidebar[data-closed] {7 width: 57px;8}910/* Style based on active state */11.my-nav-item[data-active="true"] {12 background: var(--rs-color-background-neutral-secondary);13}1415/* Animate entry and exit */16.my-overlay[data-starting-style],17.my-overlay[data-ending-style] {18 opacity: 0;19}
Common data attributes include data-open, data-closed, data-active, data-disabled, data-state, data-starting-style, and data-ending-style.
Theming with Data Attributes
The ThemeProvider sets data attributes on the root <html> element. Use these to conditionally style elements based on the active theme:
1/* Dark mode specific styles */2[data-theme="dark"] .custom-card {3 border-color: var(--rs-color-border-base-tertiary);4}56/* Traditional style variant */7[data-style="traditional"] .custom-heading {8 font-family: var(--rs-font-family-serif);9}
Available theme attributes:
| Attribute | Values |
|---|---|
data-theme | light, dark |
data-style | modern, traditional |
data-accent-color | indigo, orange, mint |
data-gray-color | gray, mauve, slate, sage |
Writing Component Styles
When building custom components in your application, follow these patterns used internally by Apsara:
Use CSS Modules for Scoping
CSS Modules prevent class name collisions and keep styles co-located with components:
1// status-card.tsx2import styles from "./status-card.module.css";34export function StatusCard({ status, children }) {5 return (6 <div className={`${styles.card} ${styles[`card-${status}`]}`}>7 {children}8 </div>9 );10}
1/* status-card.module.css */2.card {3 padding: var(--rs-space-4);4 border-radius: var(--rs-radius-3);5 border: 1px solid var(--rs-color-border-base-primary);6}78.card-success {9 border-color: var(--rs-color-border-success-emphasis);10 background: var(--rs-color-background-success-primary);11}1213.card-danger {14 border-color: var(--rs-color-border-danger-emphasis);15 background: var(--rs-color-background-danger-primary);16}
Use CVA for Variant Management
Apsara uses class-variance-authority (CVA) to manage component variants with type safety. This is recommended for components with multiple visual variations:
1import { cva, type VariantProps } from "class-variance-authority";2import styles from "./tag.module.css";34const tag = cva(styles.tag, {5 variants: {6 size: {7 small: styles["tag-small"],8 medium: styles["tag-medium"],9 },10 color: {11 neutral: styles["tag-neutral"],12 accent: styles["tag-accent"],13 },14 },15 defaultVariants: {16 size: "medium",17 color: "neutral",18 },19});2021type TagProps = VariantProps<typeof tag> & { className?: string };2223export function Tag({ size, color, className, children }: TagProps) {24 return <span className={tag({ size, color, className })}>{children}</span>;25}
CVA handles merging the base class, variant classes, and any custom className passed by consumers.
Best Practices
Use semantic tokens, not primitives. Prefer --rs-color-foreground-base-primary over --rs-neutral-12. Semantic tokens automatically adapt across themes; primitives do not carry semantic meaning.
Avoid hard-coded colors. Every color value should come from a token. This guarantees proper light/dark mode support and visual consistency.
Use spacing tokens for layout. Consistent spacing creates visual rhythm. Use --rs-space-* tokens for padding, margin, and gap values.
Keep specificity low. Use single class selectors. Avoid !important and deeply nested selectors that are hard to override.
Co-locate styles with components. Place .module.css files next to their component files. This makes it easy to find and maintain styles.