Apsara uses vanilla CSS with CSS Modules and HTML data attributes for styling. This approach provides powerful customization capabilities while maintaining predictable specificity and avoiding runtime style injection.
Styling approach
Apsara components are styled using:
CSS Modules - Scoped CSS classes that prevent naming conflicts
Data attributes - Semantic HTML attributes for styling variants and states
CSS variables - Design tokens for theming and customization
Benefits of this approach
No runtime CSS-in-JS overhead
Predictable CSS specificity
Easy to override with standard CSS
Works with any bundler that supports CSS imports
Full control over styling without JavaScript
Component CSS structure
Here’s how a typical Apsara component is styled (from button.module.css):
.button {
font-weight : var ( --rs-font-weight-medium );
font-size : var ( --rs-font-size-small );
line-height : var ( --rs-line-height-small );
box-sizing : border-box ;
display : flex ;
align-items : center ;
justify-content : center ;
border : none ;
cursor : pointer ;
padding : var ( --rs-space-3 ) var ( --rs-space-4 );
border-radius : var ( --rs-radius-2 );
transition : all 0.2 s ease-in-out ;
}
.button-small {
padding : var ( --rs-space-2 ) var ( --rs-space-3 );
font-size : var ( --rs-font-size-mini );
}
.button-normal {
padding : var ( --rs-space-3 ) var ( --rs-space-4 );
font-size : var ( --rs-font-size-small );
}
Using data attributes
Components use data attributes to handle variants and states. This makes the HTML semantic and the CSS easy to override:
/* Variant styles */
.button-solid {
color : var ( --rs-color-foreground-accent-emphasis );
background-color : var ( --rs-color-background-accent-emphasis );
}
.button-outline {
background-color : var ( --rs-color-background-base-primary );
color : var ( --rs-color-foreground-accent-primary );
border : 0.5 px solid var ( --rs-color-border-accent-emphasis );
}
/* State styles */
.button-solid:hover {
background-color : var ( --rs-color-background-accent-emphasis-hover );
}
.button:disabled ,
.button-disabled {
opacity : 0.5 ;
cursor : not-allowed ;
pointer-events : initial ;
}
Customizing component styles
There are several ways to customize component styles:
Method 1: Override CSS variables
The easiest way to customize components globally:
:root {
/* Change button padding globally */
--rs-space-3 : 10 px ;
--rs-space-4 : 16 px ;
/* Change button border radius */
--rs-radius-2 : 8 px ;
/* Change button colors */
--rs-color-background-accent-emphasis : #7c3aed ;
}
Method 2: Target component classes
Override specific components using CSS selectors:
/* Make all buttons uppercase */
.button {
text-transform : uppercase ;
letter-spacing : 0.5 px ;
}
/* Customize solid buttons */
.button-solid {
box-shadow : 0 4 px 6 px rgba ( 0 , 0 , 0 , 0.1 );
}
/* Customize specific variants */
.button-solid-danger {
background : linear-gradient ( 135 deg , #ef4444 0 % , #dc2626 100 % );
}
Method 3: Use className prop
All Apsara components accept a className prop for custom styling:
import { Button } from "@raystack/apsara" ;
import styles from "./MyButton.module.css" ;
function MyComponent () {
return (
< Button className = { styles . customButton } >
Custom Button
</ Button >
);
}
.customButton {
font-family : "Comic Sans MS" ;
border : 3 px solid #000 ;
box-shadow : 4 px 4 px 0 #000 ;
transform : rotate ( -1 deg );
}
.customButton:hover {
transform : rotate ( 0 deg );
}
Real-world examples
Example 1: Badge component
The Badge component uses size and variant classes (from badge.module.css):
.badge {
font-weight : var ( --rs-font-weight-regular );
display : inline-flex ;
padding : var ( --rs-space-1 ) var ( --rs-space-2 );
justify-content : center ;
align-items : center ;
gap : var ( --rs-space-2 );
border-radius : var ( --rs-radius-1 );
white-space : nowrap ;
}
/* Sizes */
.badge-micro {
font-size : var ( --rs-font-size-micro );
line-height : var ( --rs-line-height-micro );
height : 18 px ;
}
.badge-small {
font-size : var ( --rs-font-size-small );
line-height : var ( --rs-line-height-small );
height : 22 px ;
}
/* Variants */
.badge-neutral {
background : var ( --rs-color-background-neutral-secondary );
color : var ( --rs-color-foreground-base-primary );
}
.badge-accent {
background : var ( --rs-color-background-accent-primary );
color : var ( --rs-color-foreground-base-primary );
}
.badge-danger {
background : var ( --rs-color-background-danger-primary );
color : var ( --rs-color-foreground-base-primary );
}
Example 2: Hover and active states
Components define interactive states with pseudo-classes:
.button-outline:hover {
background-color : var ( --rs-color-background-accent-primary );
border-color : var ( --rs-color-border-accent-emphasis );
}
.button-outline:active {
background-color : var ( --rs-color-background-accent-emphasis );
color : var ( --rs-color-foreground-accent-emphasis );
border-color : var ( --rs-color-border-accent-emphasis );
}
Example 3: Radix UI state attributes
Components integrate with Radix UI’s data attributes for complex states:
/* Style button when it triggers an open popover */
.button-solid [ data-radix-popover-trigger ][ data-state = "open" ],
.button-solid [ data-radix-dropdown-menu-trigger ][ data-state = "open" ] {
background-color : var ( --rs-color-background-accent-emphasis-hover );
}
Working with CSS Modules
If you’re using CSS Modules in your project, you can import and compose Apsara styles:
@import "@raystack/apsara/style.css" ;
.myCustomButton {
composes : button from "@raystack/apsara/components/button/button.module.css" ;
/* Add your customizations */
text-decoration : underline ;
}
Be careful when composing styles from Apsara’s CSS modules as the internal structure may change between versions. Prefer using CSS variables or className overrides.
Accessibility considerations
When customizing styles, maintain accessibility:
/* Good: Maintains focus visibility */
.button:focus-visible {
outline : 2 px solid var ( --rs-color-border-accent-emphasis );
outline-offset : 2 px ;
}
/* Bad: Removes focus indicator */
.button:focus {
outline : none ; /* Don't do this! */
}
Apsara components include .sr-only utility classes for screen reader-only content. Use these for accessible icons and labels: .sr-only {
position : absolute ;
width : 1 px ;
height : 1 px ;
padding : 0 ;
margin : -1 px ;
overflow : hidden ;
clip : rect ( 0 , 0 , 0 , 0 );
white-space : nowrap ;
border : 0 ;
}
Best practices
Prefer CSS variables Use CSS variables for theming instead of hardcoding values
Maintain specificity Avoid using !important - leverage CSS specificity correctly
Test responsively Ensure custom styles work across different screen sizes
Keep accessibility Maintain focus states, contrast ratios, and semantic HTML
Related resources
Theming Customize colors, spacing, and typography
Dark mode Implement dark theme support