Preset System

A powerful theming and styling system for global component configuration with hierarchical composition and reactive state support.

Overview

Define default styles, variants, and behaviors at any level of your application hierarchy.

The Preset System is a sophisticated theming mechanism that uses Svelte's context system combined with deep merging to enable powerful composition and extension patterns. Set base themes at app root, override at route level, or customize at component level with full type safety and reactive state support.

Key Features

Hierarchical Composition

Define base themes at app root, override at route level, customize at component level

Deep Merging

Presets merge across context layers, allowing progressive enhancement

Variant Support

Define variant systems with defaults that work globally

Reactive State

Access component bond state for dynamic styling

Compound Components

Configure nested components using dot notation

Performance Optimized

Memoized resolution with early exit optimizations

Global Preset Configuration

Define base theme at application root for consistent styling.

App Root Configuration

// +layout.svelte (App Root)
import { setPreset } from '@svelte-atoms/core/context';
import { Root } from '@svelte-atoms/core/components/root';

const theme = {
  button: () => ({
    class: 'rounded-lg px-4 py-2 font-semibold transition-colors',
    variants: {
      variant: {
        primary: { class: 'bg-primary text-primary-foreground hover:bg-primary/90' },
        secondary: { class: 'bg-secondary text-secondary-foreground hover:bg-secondary/80' },
        destructive: { class: 'bg-destructive text-destructive-foreground hover:bg-destructive/90' }
      },
      size: {
        sm: { class: 'h-8 px-3 text-sm' },
        md: { class: 'h-10 px-4' },
        lg: { class: 'h-12 px-6 text-lg' }
      }
    },
    defaults: {
      variant: 'primary',
      size: 'md'
    }
  }),
  
  card: () => ({
    class: 'rounded-xl border border-border bg-card shadow-sm'
  }),
  
  'card.title': () => ({
    class: 'text-xl font-bold text-card-foreground'
  })
};

setPreset(theme);

Route-Level Overrides

Extend or override presets for specific routes and sections.

Route Layout Override

// routes/dashboard/+layout.svelte
import { setPreset } from '@svelte-atoms/core/context';

// Extends and overrides the global preset
setPreset({
  button: () => ({
    class: 'text-sm', // Merges with global button classes
    variants: {
      variant: {
        // Adds new variant, keeps primary, secondary, destructive
        info: { class: 'bg-blue-500 text-white hover:bg-blue-600' }
      }
    }
  }),
  
  card: () => ({
    class: 'bg-slate-50 border-slate-200' // Overrides global card styling
  })
});

// All buttons in /dashboard/* now have the extended variants

Component-Level Customization

Scope presets to specific component trees without affecting global theme.

Component Scoped Preset

// components/Settings.svelte
import { setPreset } from '@svelte-atoms/core/context';
import { Card } from '@svelte-atoms/core/card';

// Component-level preset for this subtree
setPreset({
  'card.title': () => ({
    class: 'text-purple-600' // Purple titles in settings only
  })
});

<Card.Root>
  <Card.Header>
    <Card.Title>Settings</Card.Title> <!-- Purple! -->
  </Card.Header>
</Card.Root>

Compound Component Presets

Configure nested components using dot notation for precise control.

Use dot notation to style compound components and their children. This is especially powerful for components like Alert, Card, Dialog, and Accordion. The pattern is 'parent.child' for precise styling control.

Dot Notation Example

Use dot notation to style compound components and their children. This is especially powerful for components like Alert, Card, Dialog, and Accordion. The pattern is 'parent.child' for precise styling control.

setPreset({
  // Parent component
  alert: () => ({
    class: 'relative rounded-lg border p-4',
    variants: {
      variant: {
        default: { class: 'bg-background text-foreground' },
        destructive: { class: 'bg-destructive/10 text-destructive border-destructive/50' },
        success: { class: 'bg-green-50 text-green-900 border-green-200' }
      }
    },
    defaults: {
      variant: 'default'
    }
  }),
  
  // Child components (dot notation)
  'alert.icon': () => ({
    class: 'h-4 w-4'
  }),
  
  'alert.title': () => ({
    class: 'mb-1 font-semibold leading-tight'
  }),
  
  'alert.description': () => ({
    class: 'text-sm leading-relaxed opacity-90'
  }),
  
  'alert.actions': () => ({
    class: 'mt-3 flex items-center gap-2'
  })
});

Reactive State-Based Styling

Access component state for dynamic, reactive preset styling.

Preset functions receive the component's bond as a parameter, giving you access to the component's reactive state. Use this to create dynamic styles that update automatically when component state changes. The function returns another function that returns the preset object, allowing for reactive class computation based on bond state.

Accordion Active State Example

Preset functions receive the component's bond as a parameter, giving you access to the component's reactive state. Use this to create dynamic styles that update automatically when component state changes. The function returns another function that returns the preset object, allowing for reactive class computation based on bond state.

'accordion.item.header': (bond) => {
  return () => ({
    class: ['', bond?.state?.isActive ? 'text-foreground/100' : 'text-foreground/50']
  });
}

The $preset Placeholder

Control exactly where preset classes are inserted in your class arrays.

Placeholder Usage

// In your component
import { HtmlAtom } from '@svelte-atoms/core';

let { class: klass = '' } = $props();

<HtmlAtom
  preset="button"
  class={[
    'my-custom-class',
    '$preset',  // Replaced with preset classes
    klass       // User classes override everything
  ]}
/>

// Result: 'my-custom-class rounded-lg px-4 py-2 font-semibold user-class'

Setting HTML Attributes

Presets can define any HTML attributes, not just classes.

Beyond styling with classes, presets can set any HTML attributes including data-*, aria-*, role, and more.

Common Use Cases:

Accessibility: Set aria-*, role, tabindex for consistent a11y
Testing: Add data-testid or data-component attributes
Analytics: Include data-analytics or tracking attributes
State Indicators: Use data-state or data-variant for CSS selectors

Attribute Configuration

// Presets can set any HTML attributes, not just classes
setPreset({
  button: () => ({
    class: 'rounded-lg px-4 py-2',
    'data-component': 'button',
    'data-version': '1.0',
    role: 'button',
    tabindex: 0
  }),
  
  'card.title': () => ({
    class: 'text-xl font-bold',
    'data-heading': 'true',
    'aria-level': 2
  }),
  
  // Variant-specific attributes
  alert: () => ({
    variants: {
      variant: {
        destructive: {
          class: 'bg-destructive/10 text-destructive',
          'data-variant': 'destructive',
          'aria-live': 'assertive',
          role: 'alert'
        },
        info: {
          class: 'bg-blue-50 text-blue-900',
          'data-variant': 'info',
          'aria-live': 'polite'
        }
      }
    }
  })
});

// Components will receive these attributes automatically
<Button>Click me</Button>
// Renders: <button class="rounded-lg px-4 py-2" data-component="button" data-version="1.0" role="button" tabindex="0">

Extending & Composing Presets

Build upon existing preset definitions with deep merging.

The preset system uses deep merging to combine configurations across context layers. This allows you to extend base presets with new variants, sizes, or completely new preset keys without losing existing definitions.

Deep Merge Example

The preset system uses deep merging to combine configurations across context layers. This allows you to extend base presets with new variants, sizes, or completely new preset keys without losing existing definitions.

// Base theme (global)
setPreset({
  button: () => ({
    class: 'rounded-md font-medium transition-colors',
    variants: {
      variant: {
        primary: { class: 'bg-blue-500 text-white' },
        secondary: { class: 'bg-gray-200 text-gray-900' }
      },
      size: {
        sm: { class: 'px-3 py-1 text-sm' },
        md: { class: 'px-4 py-2' }
      }
    }
  })
});

// Extended theme (route or component level)
setPreset({
  button: () => ({
    // Add new variants (merges with existing)
    variants: {
      variant: {
        gradient: { class: 'bg-gradient-to-r from-purple-500 to-pink-500 text-white' },
        outline: { class: 'border border-border/50 border-primary bg-transparent text-primary' }
      },
      size: {
        xl: { class: 'px-8 py-4 text-xl' } // Add new size
      }
    }
  })
});

// Result: All variants available (primary, secondary, gradient, outline)
// All sizes available (sm, md, xl)

Best Practices

Guidelines for effective preset usage and patterns.

Best Practices - Do's and Don'ts

// ✅ DO: Set global presets at app root
// +layout.svelte
setPreset({ /* base theme */ });

// ✅ DO: Override at route level for sections
// routes/admin/+layout.svelte
setPreset({ /* admin theme */ });

// ✅ DO: Use dot notation for specificity
setPreset({
  'card.title': () => ({ class: 'text-xl font-bold' })
});

// ✅ DO: Access bond state for reactivity
setPreset({
  'accordion.item.header': (bond) => ({
    class: bond?.state?.isOpen ? 'bg-accent' : 'bg-background'
  })
});

// ❌ DON'T: Override presets for one-off styling
// Use component props instead
<Button class="custom-one-off-style">Click</Button>

// ❌ DON'T: Create overly complex preset functions
// Keep preset logic simple and focused
1.

Set global presets at app root

Define your base theme in +layout.svelte for consistent styling

2.

Override at route level (future)

Use route layouts to customize sections of your app without affecting global theme

3.

Use component-level sparingly

Only when specific component trees need unique styling that can't be achieved at route level

4.

Leverage dot notation for specificity

'card.title' is more specific than 'card'

5.

Access bond state for reactivity

Use the bond parameter in preset functions for dynamic, state-driven styles

6.

Merge, don't replace

Presets merge across contexts - build up configurations gradually instead of replacing

7.

Keep presets simple

Presets are for common patterns; use component props for one-off customizations

Common Use Cases

Real-world scenarios where presets excel.

Multi-Tenant Applications

Different organizations can have their own branded themes by setting presets based on tenant configuration.

Dark Mode Implementation

Toggle between light and dark presets at the root level to implement theme switching.

Dashboard vs Marketing Site

Use route-level presets for compact dashboard styles and spacious marketing page layouts.

Component Library Theming

Provide default presets with your library that consumers can easily override or extend.

A/B Testing Styles

Conditionally apply different presets based on user segments or feature flags.

Responsive Styling

Adjust component variants based on viewport size or device capabilities.

API Reference

Core functions and types for the preset system.

setPreset(preset)

Sets or merges preset configuration in the current context.

setPreset(preset: Partial<Preset>): void

getPreset(key?)

Retrieves preset configuration. If key is provided, returns specific preset entry; otherwise returns all presets.

getPreset<K>(key?: K): PresetEntry | Partial<Preset>

Preset Entry Function

Preset entries are functions that receive the component bond and return configuration objects.

type PresetEntry = (bond: Bond | null) => PresetEntryRecord

PresetEntryRecord

The object returned by preset functions. Supports any HTML attributes including class, data-*, aria-*, role, etc.

{
  class?: ClassValue;
  as?: string;
  base?: Base;
  variants?: {
    [variantName: string]: {
      [variantValue: string]: any; // Can include class, data-*, aria-*, etc.
    };
  };
  compounds?: Array<Record<string, any>>;
  defaults?: Record<string, any>;
  // Any additional HTML attributes:
  // 'data-*'?: string;
  // 'aria-*'?: string;
  // role?: string;
  // tabindex?: number;
  // etc.
}