Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/svelte-atoms/core/llms.txt

Use this file to discover all available pages before exploring further.

Overview

Svelte Atoms Core is headless by default, providing no opinionated styling. This gives you complete freedom to style components using your preferred approach: TailwindCSS, CSS modules, custom CSS, or any styling solution.
All components accept a class prop that supports strings, arrays, and objects for maximum flexibility.

TailwindCSS Integration

Svelte Atoms has first-class TailwindCSS support with powerful class merging and preset systems.

Basic Usage

<script>
  import { Button } from '@svelte-atoms/core/components/button';
</script>

<Button.Root class="rounded-lg bg-blue-500 px-6 py-3 text-white hover:bg-blue-600">
  Click me
</Button.Root>

Class Arrays

Pass arrays of classes for conditional styling:
<script>
  import { Badge } from '@svelte-atoms/core/components/badge';
  
  let variant = $state<'info' | 'warning' | 'error'>('info');
  
  const classes = $derived([
    'rounded-full px-3 py-1 text-sm font-medium',
    variant === 'info' && 'bg-blue-100 text-blue-800',
    variant === 'warning' && 'bg-yellow-100 text-yellow-800',
    variant === 'error' && 'bg-red-100 text-red-800',
  ]);
</script>

<Badge.Root class={classes}>
  Status: {variant}
</Badge.Root>

Dynamic Classes with cn Utility

Use the built-in cn utility for conditional classes:
<script>
  import { Button } from '@svelte-atoms/core/components/button';
  import { cn } from '@svelte-atoms/core/utils';
  
  let isActive = $state(false);
  let isLoading = $state(false);
</script>

<Button.Root 
  class={cn(
    'rounded-lg px-4 py-2',
    isActive && 'bg-blue-500 text-white',
    !isActive && 'bg-gray-200 text-gray-800',
    isLoading && 'cursor-wait opacity-50'
  )}
>
  {isLoading ? 'Loading...' : 'Submit'}
</Button.Root>

Preset System

The preset system enables consistent styling across your application with reusable configurations.

Creating a Preset

// presets/button.ts
export const buttonPreset = {
  button: {
    // Base classes applied to all buttons
    class: 'rounded-md px-4 py-2 font-medium transition-colors',
    
    // Variant system
    variants: {
      variant: {
        primary: 'bg-blue-500 text-white hover:bg-blue-600',
        secondary: 'bg-gray-200 text-gray-800 hover:bg-gray-300',
        danger: 'bg-red-500 text-white hover:bg-red-600',
      },
      size: {
        sm: 'px-2 py-1 text-sm',
        md: 'px-4 py-2 text-base',
        lg: 'px-6 py-3 text-lg',
      },
    },
    
    // Compound variants for combinations
    compounds: [
      {
        variant: 'primary',
        size: 'lg',
        class: 'shadow-lg',
      },
    ],
    
    // Default variant values
    defaults: {
      variant: 'primary',
      size: 'md',
    },
  },
};

Using Presets

<script>
  import { Root } from '@svelte-atoms/core/components/root';
  import { Button } from '@svelte-atoms/core/components/button';
  import { buttonPreset } from './presets/button';
</script>

<Root presets={{ button: buttonPreset }}>
  <!-- Uses default: variant="primary" size="md" -->
  <Button.Root>Default Button</Button.Root>
  
  <!-- Override variants -->
  <Button.Root variant="secondary" size="lg">
    Large Secondary
  </Button.Root>
  
  <!-- Compound variant automatically applies shadow-lg -->
  <Button.Root variant="primary" size="lg">
    Large Primary with Shadow
  </Button.Root>
</Root>

The $preset Placeholder

Control where preset classes are inserted using the $preset placeholder:
<script>
  import { Button } from '@svelte-atoms/core/components/button';
</script>

<!-- Preset classes inserted at $preset position -->
<Button.Root class="flex items-center gap-2 $preset font-bold">
  Custom Order
</Button.Root>

<!-- Without $preset, variants override direct classes -->
<Button.Root class="flex items-center gap-2 font-bold">
  Default Order
</Button.Root>
The $preset placeholder gives you precise control over class precedence and allows you to override preset classes with your own.

Variant System

Define variants directly on components:
<script lang="ts">
  import { HtmlAtom } from '@svelte-atoms/core';
  import type { VariantDefinition } from '@svelte-atoms/core/utils';
  
  type Props = {
    variant?: 'solid' | 'outline' | 'ghost';
    size?: 'sm' | 'md' | 'lg';
  };
  
  let { variant = 'solid', size = 'md', ...rest }: Props = $props();
  
  const variants: VariantDefinition<Props> = {
    class: 'rounded transition-colors',
    variants: {
      variant: {
        solid: 'bg-blue-500 text-white',
        outline: 'border-2 border-blue-500 text-blue-500',
        ghost: 'text-blue-500 hover:bg-blue-50',
      },
      size: {
        sm: 'px-2 py-1 text-sm',
        md: 'px-4 py-2 text-base',
        lg: 'px-6 py-3 text-lg',
      },
    },
    compounds: [
      {
        variant: 'outline',
        size: 'lg',
        class: 'border-4',
      },
    ],
    defaults: {
      variant: 'solid',
      size: 'md',
    },
  };
</script>

<HtmlAtom as="button" {variants} {variant} {size} {...rest}>
  {@render children?.()}
</HtmlAtom>
Use the component:
<CustomButton variant="outline" size="lg">
  Outlined Large Button
</CustomButton>

CSS Modules

Use CSS modules for scoped styling:
/* Button.module.css */
.button {
  display: inline-flex;
  align-items: center;
  padding: 0.5rem 1rem;
  border-radius: 0.375rem;
  font-weight: 500;
  transition: all 0.2s;
}

.button:hover {
  transform: translateY(-2px);
  box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}

.primary {
  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
  color: white;
}

.secondary {
  background: #f3f4f6;
  color: #1f2937;
}
<script>
  import { Button } from '@svelte-atoms/core/components/button';
  import styles from './Button.module.css';
  
  let variant = $state('primary');
</script>

<Button.Root class="{styles.button} {styles[variant]}">
  Styled Button
</Button.Root>

Custom CSS

Use traditional CSS with custom classes:
<script>
  import { Card } from '@svelte-atoms/core/components/card';
</script>

<Card.Root class="custom-card">
  <Card.Header class="custom-card-header">
    <h2>Card Title</h2>
  </Card.Header>
  <Card.Body class="custom-card-body">
    Content here
  </Card.Body>
</Card.Root>

<style>
  :global(.custom-card) {
    background: white;
    border-radius: 12px;
    box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1);
    overflow: hidden;
  }
  
  :global(.custom-card-header) {
    background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
    color: white;
    padding: 1.5rem;
  }
  
  :global(.custom-card-body) {
    padding: 1.5rem;
  }
</style>

Styled Components Pattern

Create pre-styled components:
<!-- PrimaryButton.svelte -->
<script lang="ts">
  import { Button } from '@svelte-atoms/core/components/button';
  
  let { class: className = '', ...rest } = $props();
</script>

<Button.Root 
  class="rounded-lg bg-gradient-to-r from-blue-500 to-purple-600 px-6 py-3 font-bold text-white shadow-lg transition-all hover:scale-105 hover:shadow-xl {className}"
  {...rest}
>
  {@render children?.()}
</Button.Root>
Use it:
<script>
  import PrimaryButton from './PrimaryButton.svelte';
</script>

<PrimaryButton onclick={() => console.log('clicked')}>
  Click Me
</PrimaryButton>

<!-- Override with additional classes -->
<PrimaryButton class="mt-4 w-full">
  Full Width
</PrimaryButton>

Theming

CSS Variables

Define themes with CSS variables:
/* global.css */
:root {
  --color-primary: #3b82f6;
  --color-primary-hover: #2563eb;
  --color-secondary: #6b7280;
  --color-danger: #ef4444;
  --color-success: #10b981;
  
  --spacing-xs: 0.25rem;
  --spacing-sm: 0.5rem;
  --spacing-md: 1rem;
  --spacing-lg: 1.5rem;
  
  --radius-sm: 0.25rem;
  --radius-md: 0.5rem;
  --radius-lg: 0.75rem;
}

[data-theme="dark"] {
  --color-primary: #60a5fa;
  --color-primary-hover: #3b82f6;
  --color-secondary: #9ca3af;
}
<script>
  import { Button } from '@svelte-atoms/core/components/button';
  
  let theme = $state('light');
</script>

<div data-theme={theme}>
  <Button.Root style="background-color: var(--color-primary);">
    Themed Button
  </Button.Root>
</div>

TailwindCSS Dark Mode

<script>
  import { Card } from '@svelte-atoms/core/components/card';
</script>

<Card.Root class="bg-white dark:bg-gray-800">
  <Card.Header class="border-b border-gray-200 dark:border-gray-700">
    <h2 class="text-gray-900 dark:text-white">Dark Mode Support</h2>
  </Card.Header>
  <Card.Body class="text-gray-700 dark:text-gray-300">
    Content that adapts to dark mode
  </Card.Body>
</Card.Root>

Real-World Example: Button System

Create a comprehensive button system:
<!-- AppButton.svelte -->
<script lang="ts">
  import { Button } from '@svelte-atoms/core/components/button';
  import { cn } from '@svelte-atoms/core/utils';
  
  type Variant = 'primary' | 'secondary' | 'danger' | 'ghost';
  type Size = 'sm' | 'md' | 'lg';
  
  type Props = {
    variant?: Variant;
    size?: Size;
    loading?: boolean;
    icon?: Component;
    class?: string;
  };
  
  let {
    variant = 'primary',
    size = 'md',
    loading = false,
    icon,
    class: className = '',
    children,
    ...rest
  }: Props = $props();
  
  const baseClasses = 'inline-flex items-center justify-center gap-2 rounded-md font-medium transition-all disabled:cursor-not-allowed disabled:opacity-50';
  
  const variantClasses: Record<Variant, string> = {
    primary: 'bg-blue-600 text-white hover:bg-blue-700 active:bg-blue-800 shadow-sm hover:shadow-md',
    secondary: 'bg-gray-200 text-gray-900 hover:bg-gray-300 active:bg-gray-400',
    danger: 'bg-red-600 text-white hover:bg-red-700 active:bg-red-800 shadow-sm hover:shadow-md',
    ghost: 'bg-transparent text-gray-700 hover:bg-gray-100 active:bg-gray-200',
  };
  
  const sizeClasses: Record<Size, string> = {
    sm: 'px-3 py-1.5 text-sm',
    md: 'px-4 py-2 text-base',
    lg: 'px-6 py-3 text-lg',
  };
  
  const finalClasses = cn(
    baseClasses,
    variantClasses[variant],
    sizeClasses[size],
    loading && 'cursor-wait',
    className
  );
</script>

<Button.Root class={finalClasses} disabled={loading} {...rest}>
  {#if loading}
    <svg class="h-4 w-4 animate-spin" viewBox="0 0 24 24">
      <circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4" fill="none" />
      <path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z" />
    </svg>
  {/if}
  
  {#if icon && !loading}
    <svelte:component this={icon} class="h-4 w-4" />
  {/if}
  
  {@render children?.()}
</Button.Root>
Use the button system:
<script>
  import AppButton from './AppButton.svelte';
  import { IconTrash, IconEdit } from './icons';
  
  let loading = $state(false);
  
  async function handleSubmit() {
    loading = true;
    await new Promise(r => setTimeout(r, 2000));
    loading = false;
  }
</script>

<div class="flex gap-2">
  <AppButton variant="primary" onclick={handleSubmit} loading={loading}>
    Submit Form
  </AppButton>
  
  <AppButton variant="secondary" size="sm" icon={IconEdit}>
    Edit
  </AppButton>
  
  <AppButton variant="danger" icon={IconTrash}>
    Delete
  </AppButton>
  
  <AppButton variant="ghost">
    Cancel
  </AppButton>
</div>

Styling Best Practices

Define a spacing scale and stick to it. TailwindCSS provides this by default, or define your own:
--spacing-1: 0.25rem;  /* 4px */
--spacing-2: 0.5rem;   /* 8px */
--spacing-3: 0.75rem;  /* 12px */
--spacing-4: 1rem;     /* 16px */
Create styled wrapper components instead of repeating classes:
<!-- ✅ Good -->
<PrimaryButton>Submit</PrimaryButton>

<!-- ❌ Avoid -->
<Button.Root class="rounded-lg bg-blue-500 px-6 py-3 text-white">
  Submit
</Button.Root>
<!-- ✅ Good: Semantic -->
<Button.Root class="bg-primary text-primary-foreground">
  Primary Action
</Button.Root>

<!-- ❌ Avoid: Color-specific -->
<Button.Root class="bg-blue-500 text-white">
  Primary Action
</Button.Root>
Avoid inline styles unless absolutely necessary. Use classes for better performance and maintainability.

Next Steps

Accessibility

Learn about built-in accessibility features

Animations

Add animations with lifecycle hooks

Composition

Master component composition patterns

Components

Browse all available components

Build docs developers (and LLMs) love