Styling System
A comprehensive guide to styling components with TailwindCSS, variants, presets, and the cn() utility.
Overview
The styling system combines utility-first CSS with powerful variant definitions and global theming.
All components accept a class prop that supports strings, arrays with conditionals, and objects. The styling system is built
on TailwindCSS utility classes, enhanced with the cn() utility for intelligent
class merging, and integrated with the preset system for global theming.
Class Prop Formats
String
class="bg-primary text-white"Array
class={['base', isActive && 'active']}Object
class={{ 'active': isActive }}Mixed
class={['base', condition && 'class', klass]}TailwindCSS Utilities
Use Tailwind utility classes for 90% of your styling needs with full responsive support.
Common Patterns
<!-- Layout & spacing -->
<Card.Root class="max-w-sm p-4">
<Card.Header>
<Card.Title class="text-lg font-semibold">Title</Card.Title>
</Card.Header>
</Card.Root>
<!-- Interactive states -->
<button class="bg-primary px-4 py-2 text-primary-foreground hover:bg-primary/90 active:bg-primary/100">
Action
</button>
<!-- Responsive -->
<h2 class="text-2xl md:text-3xl lg:text-4xl">Responsive heading</h2>
<!-- With opacity -->
<div class="bg-foreground/10">Subtle background</div>The cn() Utility
Intelligent class merging with automatic conflict resolution using clsx and tailwind-merge.
Usage Examples
The cn() utility
combines clsx for conditional class handling with tailwind-merge for
intelligent Tailwind class conflict resolution. When conflicting utility classes are provided,
the last one wins.
import { cn } from '@svelte-atoms/core/utils';
// Resolves conflicts automatically
cn('px-2 py-1', 'px-4');
// Result: 'py-1 px-4'
// Handles conditionals
cn('base', isActive && 'active', false && 'ignored');
// Result: 'base active'
// Merges arrays
cn(['text-sm', 'font-medium'], 'text-lg');
// Result: 'font-medium text-lg'CSS Custom Properties
Use theme color tokens for consistent, theme-aware styling across your application. The library uses a token system following shadcn/ui conventions for maximum compatibility and familiarity.
Available Color Tokens
background / foregroundprimary / primary-foregroundsecondary / secondary-foregroundmuted / muted-foregroundaccent / accent-foregrounddestructive / destructive-foregroundcard / card-foregroundborder / input / ringColor Token Usage
<!-- Color tokens -->
<div class="bg-background text-foreground">Background color</div>
<Button class="bg-primary text-primary-foreground">Primary button</Button>
<!-- With opacity -->
<div class="bg-foreground/10 text-foreground/50">Subtle styling</div>
<!-- Borders and shadows -->
<Card.Root class="border-border border shadow-lg">Card</Card.Root>
<!-- All available colors -->
<div class="bg-secondary text-secondary-foreground">Secondary</div>
<div class="bg-muted text-muted-foreground">Muted</div>
<div class="bg-accent text-accent-foreground">Accent</div>
<div class="bg-destructive text-destructive-foreground">Destructive</div>Conditional Styling
Apply classes conditionally based on component state or props using arrays and ternary operators.
Usage Examples
<script>
let isOpen = $state(false);
let isActive = $state(true);
</script>
<!-- Array with conditions -->
<Collapsible.Body
class={[
'pointer-events-none h-0 opacity-0',
isOpen && 'pointer-events-auto h-auto opacity-100'
]}
>
Content
</Collapsible.Body>
<!-- Ternary -->
<Tab.Root class={isActive ? 'opacity-100' : 'opacity-50'}>
Tab content
</Tab.Root>
<!-- Multiple conditions -->
<Button
class={[
'base-button',
isActive && 'bg-primary',
isDisabled && 'opacity-50 pointer-events-none',
size === 'large' && 'px-6 py-3'
]}
>
Button
</Button>Variant System
Define reusable component variants with full TypeScript support and bond state access.
Local vs Global Variants
Variants can be defined in two ways:
defineVariants(). This approach is described below and is ideal for component-specific styling that
doesn't need to be shared across your application.+layout.svelte file. This allows you to set consistent styling for all components of a specific type
throughout your application. See the Preset Documentation for details on global variant configuration.The defineVariants() utility provides a powerful way to create component styling variations at the component level.
Variants can define multiple styling dimensions (size, variant, state), support compound variants
for conditional styling when multiple conditions match, and access component bond state for reactive
styling.
This example shows how to define variants at the component level. For global variants that apply across your entire application, use the preset system instead.
Basic Variant Definition
<script lang="ts">
import { defineVariants } from '@svelte-atoms/core/utils';
import { HtmlAtom } from '@svelte-atoms/core';
const buttonVariants = defineVariants({
class: 'inline-flex items-center justify-center rounded-md font-medium',
variants: {
variant: {
primary: 'bg-primary text-primary-foreground hover:bg-primary/90',
secondary: 'bg-secondary text-secondary-foreground hover:bg-secondary/90',
ghost: 'hover:bg-accent hover:text-accent-foreground'
},
size: {
sm: 'h-8 px-3 text-sm',
md: 'h-10 px-4',
lg: 'h-12 px-6 text-lg'
}
},
defaults: {
variant: 'primary',
size: 'md'
}
});
let { variant, size, ...props } = $props();
</script>
<HtmlAtom variants={buttonVariants} {variant} {size} {...props}>
{@render children?.()}
</HtmlAtom>Key Features
Base Classes
Define base styling that applies to all variants
Multiple Dimensions
Combine variant, size, state, and custom dimensions
Default Values
Set sensible defaults for all variant dimensions
Type Safety
Automatic TypeScript inference for all variant options
Compound Variants
Apply styling when multiple conditions match
Attribute Support
Return aria-*, data-*, and other HTML attributes
Compound Variants
Apply additional styling when multiple variant conditions match simultaneously.
Usage Example
Compound variants allow you to define styling that only applies when specific combinations
of variant values are active. This is useful for edge cases or special styling requirements
that go beyond single-dimension variants. Compounds can also set HTML attributes like role and aria-* attributes.
const alertVariants = defineVariants({
class: 'rounded-lg p-4 border',
variants: {
variant: {
error: 'bg-destructive/10 border-destructive/50 text-destructive'
},
size: {
lg: 'text-lg p-6'
}
},
compounds: [
{
variant: 'error',
size: 'lg',
class: 'font-bold',
role: 'alert',
'aria-live': 'assertive'
}
]
});Reactive Variants
Access component bond state for dynamic, reactive variant styling.
Usage Example
When you need variants to respond to component state, pass a function to defineVariants() that receives the component's bond. This enables reactive styling based on internal state like isOpen, isActive, or isDisabled.
const accordionVariants = defineVariants((bond) => ({
class: 'border rounded-md transition-all',
variants: {
state: {
open: {
class: bond?.state?.isOpen ? 'bg-primary/5 border-primary' : 'bg-card',
'aria-expanded': bond?.state?.isOpen,
'data-state': bond?.state?.isOpen ? 'open' : 'closed'
}
}
}
}));The $preset Placeholder
Control exactly where preset classes are inserted in your class arrays.
Usage Example
When using presets, the special '$preset' string in your class array will be replaced with preset classes at that exact position. If no '$preset' placeholder is present, preset classes are automatically prepended to your class array.
<!-- In your component -->
<HtmlAtom
preset="button"
class={[
'component-defaults',
'$preset', // Replaced with preset classes
klass // User classes override
]}
/>
<!-- Without $preset (preset at start) -->
<HtmlAtom
preset="button"
class={['component-classes', klass]}
/>
<!-- Result: preset-classes component-classes user-classes -->Inline Styles
Use inline styles sparingly, only for truly dynamic values that cannot be expressed with classes.
Inline styles should be reserved for values that change frequently or cannot be predetermined, such as dynamic widths, transforms, or opacity values driven by user interaction or animation. For static styling, always prefer Tailwind utility classes.
When to Use Inline Styles:
Best Practices
Guidelines for effective styling that leads to maintainable, performant applications.
Prefer Tailwind Utilities
Use TailwindCSS classes for 90% of styling needs. They're optimized, purged in production, and provide consistent design tokens.
User Classes Override Last
Always place user-provided classes (like the class prop) last in your class array to ensure they can override component defaults and preset styles.
Use Presets for Consistency
Define component defaults once in presets rather than repeating classes across your application. This makes theme changes easier.
Leverage cn() for Merging
Always use cn() when merging classes. It handles conflicts intelligently and works with conditionals.
Avoid Inline Styles
Reserve inline styles for truly dynamic values only. Static styling should always use utility classes for better performance and maintainability.
Keep Specificity Low
Avoid !important and overly specific selectors. Let the cascade and class order handle overrides naturally.
Use Semantic Color Tokens
Prefer semantic tokens like bg-primary over specific colors like bg-blue-500. This ensures theme compatibility.
Extract Reusable Variants
When you find yourself repeating the same variant combinations, extract them into a defineVariants() definition.