Skip to content

Styling

Datastar provides attributes for controlling CSS classes and inline styles dynamically based on signal values. These attributes let you create responsive interfaces that change appearance as your application state changes, without writing JavaScript event handlers or DOM manipulation code.

Conditional Classes

data-class:* (Datastar)

The data-class:* attribute adds or removes a CSS class based on whether an expression evaluates to true. Replace * with the class name you want to control:

blade
<div data-signals="{isActive: true}">
    <button data-class:active="$isActive">
        Click Me
    </button>
</div>

When isActive is true, the button has the active class. When isActive becomes false, the class is removed automatically.

Multiple Classes

You can control multiple classes independently:

blade
<div data-signals="{
    isDisabled: true,
    hasError: false
}">
    <button
        data-class="{
            'cursor-not-allowed': $isDisabled,
            'text-red-500': $hasError
        }"
        class="px-4 py-2 rounded">
        Submit
    </button>
</div>

Expressions

The condition can be any JavaScript expression:

blade
<div data-signals="{count: 12}">
    <div 
    data-class="{'bg-yellow-500 text-white': $count > 10}">
        Count: <span data-text="$count"></span>
    </div>
</div>

The bg-yellow-500, text-white classes are added when count exceeds 10.

Multiple Class Names

You can add or remove multiple classes together by separating them with spaces:

blade
<div data-signals="{isPremium: true}">
    <div 
    data-class="{'bg-amber-600 text-white font-bold': $isPremium}"
    >
        Premium Content
    </div>
</div>

When isPremium is true, all three classes (bg-amber-600, text-white, font-bold) are added. When false, all are removed.

Dynamic Inline Styles

data-style:* (Datastar)

The data-style:* attribute sets inline CSS properties dynamically. Replace * with the CSS property name:

blade
<div data-signals="{progress: 75}">
    <div
        data-style:width="$progress + '%'"
        class="bg-blue-500 h-4 rounded">
    </div>
</div>

As progress changes, the width style updates immediately. Use standard CSS property names in kebab-case.

Multiple Style Properties

Control different properties independently:

blade
<div data-signals="{
    boxColor: 'blue',
    boxSize: 100,
    boxOpacity: 0.8
}">
    <div
        data-style:background-color="$boxColor"
        data-style:width="$boxSize + 'px'"
        data-style:height="$boxSize + 'px'"
        data-style:opacity="$boxOpacity">
    </div>
</div>

Conditional Styles

Use ternary operators to conditionally set styles:

blade
<div data-signals="{theme: 'dark'}">
    <div data-style:color="$theme === 'dark' ? 'white' : 'black'"
         data-style:background-color="$theme === 'dark' ? '#1a1a1a' : '#ffffff'">
        Content adapts to theme
    </div>
</div>

Common Patterns

blade
<div data-signals="{currentPage: 'home'}">
    <nav>
        <a href="/home"
           data-class:active="$currentPage === 'home'"
           data-on:click="$currentPage = 'home'">
            Home
        </a>
        <a href="/about"
           data-class:active="$currentPage === 'about'"
           data-on:click="$currentPage = 'about'">
            About
        </a>
        <a href="/contact"
           data-class:active="$currentPage === 'contact'"
           data-on:click="$currentPage = 'contact'">
            Contact
        </a>
    </nav>
</div>

<style>
    .active {
        font-weight: bold;
        color: blue;
        border-bottom: 2px solid blue;
    }
</style>

Form Validation States

blade
<div data-signals="{
    email: '',
    errors: {}
}">
    <input
        type="email"
        data-bind="email"
        data-class:border-red-500="$errors.email"
        data-class:border-gray-300="!$errors.email"
        class="border-2 rounded px-3 py-2" />

    <div data-error="email" class="text-red-500 text-sm"></div>
</div>

The input border turns red when there's an error, gray otherwise.

Loading States

blade
<div data-signals="{isLoading: false}">
    <button
        data-class:opacity-50="$isLoading"
        data-class:cursor-not-allowed="$isLoading"
        data-attr:disabled="$isLoading"
        data-on:click="$isLoading = true">
        <span data-show="!$isLoading">Submit</span>
        <span data-show="$isLoading">Processing...</span>
    </button>
</div>

Theme Switching

blade
<div data-signals="{theme: 'dark'}">
    <div
        data-class="{
            'bg-white text-black': $theme === 'light',
            'bg-gray-900 text-white': $theme === 'dark'
        }" class="min-h-screen p-8">
        

        <button
            data-on:click="$theme = ($theme === 'light' ? 'dark' : 'light')"
            class="px-4 py-2 rounded cursor-pointer">
            Toggle Theme 
        </button>
        <div>Current: <span data-text="$theme"></span></div>

        <div class="mt-8">
            <h1 class="text-3xl font-bold">Themed Content</h1>
            <p class="mt-4">This content adapts to the current theme.</p>
        </div>
    </div>
</div>

Status Indicators

blade
<div data-signals="{status: 'pending'}">
    <span
        class="px-3 py-1 rounded-full text-sm font-medium transition-all duration-300"
        data-class="{
            'bg-yellow-100 text-yellow-800': $status === 'pending',
            'bg-green-100 text-green-800': $status === 'completed',
            'bg-red-100 text-red-800': $status === 'failed'
        }">
        <span data-text="$status"></span>
    </span>
</div>

Hover Effects with State

blade
<div data-signals="{isHovered: false}">
    <div
        data-on:mouseenter="$isHovered = true"
        data-on:mouseleave="$isHovered = false"
        data-class:shadow-lg="$isHovered"
        data-class:scale-105="$isHovered"
        class="p-6 border rounded transition-all duration-300">
        Hover over me
    </div>
</div>

Working with Tailwind CSS

Datastar's styling attributes work seamlessly with Tailwind CSS:

blade
<div data-signals="{priority: 'high'}">
    <div
        class="text-black px-4 py-2 rounded"
        data-class="{
            'bg-red-500': $priority === 'low',
            'bg-yellow-500': $priority === 'medium',
            'bg-green-500': $priority === 'high'
        }">
        Priority: <span data-text="$priority"></span>
    </div>
</div>

Dark Mode

Combine with Tailwind's dark mode classes:

blade
<div data-signals="{darkMode: false}">
    <div data-class:dark="$darkMode">
        <div class="bg-white dark:bg-gray-800 text-black dark:text-white p-8">
            This content respects dark mode
        </div>
    </div>
</div>

Transitions

data-transition (Hyper)

The data-transition attribute adds smooth animations when showing and hiding elements with data-show. It supports three modes: helper mode for quick style-based transitions, class mode for Tailwind CSS integration, and collapse mode for height animations.

Helper Mode (Style-based)

The simplest way to add transitions. Just add data-transition to any element with data-show:

blade
<div data-signals="{visible: false}">
    <button data-on:click="$visible = !$visible">Toggle</button>

    <div data-show="$visible" data-transition>
        This content fades and scales smoothly
    </div>
</div>

By default, this creates a subtle fade + scale animation (150ms enter, 75ms leave).

Helper Modifiers

Customize the transition with modifiers:

blade
<!-- Opacity only (no scale) -->
<div data-show="$visible" data-transition__opacity>
    Fade only
</div>

<!-- Scale only (no fade) -->
<div data-show="$visible" data-transition__scale>
    Scale only
</div>

<!-- Custom duration (300ms) -->
<div data-show="$visible" data-transition__duration__300>
    Slower animation
</div>

<!-- Custom scale amount (90% instead of default 95%) -->
<div data-show="$visible" data-transition__scale__90>
    More dramatic scale
</div>

<!-- Transform origin -->
<div data-show="$visible" data-transition__origin__top>
    Scales from top
</div>

<!-- Delayed start -->
<div data-show="$visible" data-transition__delay__100>
    Starts after 100ms
</div>

<!-- Enter only (no leave animation) -->
<div data-show="$visible" data-transition__in>
    Only animates when appearing
</div>

<!-- Leave only (no enter animation) -->
<div data-show="$visible" data-transition__out>
    Only animates when disappearing
</div>

Combining Modifiers

Chain multiple modifiers for precise control:

blade
<div data-show="$visible"
     data-transition__opacity__duration__500__origin__top__delay__100>
    Customized transition
</div>

Available Helper Modifiers

ModifierDescriptionDefault
__opacityInclude opacity transitiontrue
__scaleInclude scale transitiontrue
__scale__[value]Custom scale value (0-100)95
__duration__[ms]Duration in milliseconds150 enter, 75 leave
__delay__[ms]Delay before starting0
__origin__[pos]Transform origincenter
__inApply only on enter (show)-
__outApply only on leave (hide)-

Class Mode (Tailwind CSS)

For more control, use class-based transitions with Tailwind CSS or custom classes. This mode supports all 6 transition stages:

blade
<div data-show="$visible"
     data-transition:enter="transition ease-out duration-300"
     data-transition:enter-start="opacity-0 scale-90"
     data-transition:enter-end="opacity-100 scale-100"
     data-transition:leave="transition ease-in duration-200"
     data-transition:leave-start="opacity-100 scale-100"
     data-transition:leave-end="opacity-0 scale-90">
    Tailwind-animated content
</div>

Transition Stages

StageWhen AppliedPurpose
enterDuring entire enter animationTransition properties
enter-startBefore element appearsInitial state (hidden)
enter-endAfter first frameFinal state (visible)
leaveDuring entire leave animationTransition properties
leave-startBefore element disappearsInitial state (visible)
leave-endAfter first frameFinal state (hidden)

Minimal Class Mode

You don't need all 6 stages. Often just enter and leave are enough:

blade
<div data-show="$visible"
     data-transition:enter="transition-opacity duration-300"
     data-transition:leave="transition-opacity duration-200">
    Simple fade
</div>

Slide Animations

blade
<!-- Slide down -->
<div data-show="$visible"
     data-transition:enter="transition-all duration-300 ease-out"
     data-transition:enter-start="opacity-0 -translate-y-4"
     data-transition:enter-end="opacity-100 translate-y-0"
     data-transition:leave="transition-all duration-200 ease-in"
     data-transition:leave-start="opacity-100 translate-y-0"
     data-transition:leave-end="opacity-0 -translate-y-4">
    Slides down when appearing
</div>

<!-- Slide from right -->
<div data-show="$visible"
     data-transition:enter="transition-all duration-300"
     data-transition:enter-start="opacity-0 translate-x-4"
     data-transition:enter-end="opacity-100 translate-x-0"
     data-transition:leave="transition-all duration-200"
     data-transition:leave-start="opacity-100 translate-x-0"
     data-transition:leave-end="opacity-0 translate-x-4">
    Slides from right
</div>

Collapse Mode (Height Animations)

For accordions and expandable content, use collapse mode to animate height. CSS can't transition height: auto, so this mode uses JavaScript to measure and animate smoothly:

blade
<div data-signals="{expanded: false}">
    <button data-on:click="$expanded = !$expanded">
        Toggle Content
    </button>

    <div data-show="$expanded" data-transition__collapse>
        <p>This content smoothly expands and collapses.</p>
        <p>The height animates from 0 to auto.</p>
    </div>
</div>

Collapse Modifiers

blade
<!-- Custom duration (500ms instead of default 250ms) -->
<div data-show="$expanded" data-transition__collapse__duration__500>
    Slower collapse
</div>

<!-- Minimum height (doesn't fully hide) -->
<div data-show="$expanded" data-transition__collapse__min__50>
    Shows at least 50px when collapsed
</div>

<!-- Combined -->
<div data-show="$expanded" data-transition__collapse__duration__300__min__100>
    Custom duration and min height
</div>

Accordion Pattern

blade
<div data-signals="{activePanel: null}">
    <!-- Panel 1 -->
    <button data-on:click="$activePanel = $activePanel === 1 ? null : 1">
        Section 1
    </button>
    <div data-show="$activePanel === 1" data-transition__collapse>
        <p>Content for section 1</p>
    </div>

    <!-- Panel 2 -->
    <button data-on:click="$activePanel = $activePanel === 2 ? null : 2">
        Section 2
    </button>
    <div data-show="$activePanel === 2" data-transition__collapse>
        <p>Content for section 2</p>
    </div>

    <!-- Panel 3 -->
    <button data-on:click="$activePanel = $activePanel === 3 ? null : 3">
        Section 3
    </button>
    <div data-show="$activePanel === 3" data-transition__collapse>
        <p>Content for section 3</p>
    </div>
</div>

"Read More" Pattern

Use min-height to show a preview that expands:

blade
<div data-signals="{showFull: false}">
    <div data-show="true" data-transition__collapse__min__80>
        <p>This is a long article with lots of content...</p>
        <p>More paragraphs here...</p>
        <p>And even more content below...</p>
    </div>

    <button data-on:click="$showFull = !$showFull">
        <span data-show="!$showFull">Read More</span>
        <span data-show="$showFull">Show Less</span>
    </button>
</div>

TIP

Collapse transitions handle rapid toggling gracefully—if you click multiple times quickly, the animation smoothly reverses direction without glitches.

Collapse Modifier Reference

ModifierDescriptionDefault
__collapseEnable collapse mode-
__duration__[ms]Animation duration250ms
__min__[px]Minimum collapsed height0 (fully hidden)

Transition + Show Integration

All transition modes work seamlessly with data-show. The transition handles:

  1. Enter: When data-show becomes true, runs enter animation then displays
  2. Leave: When data-show becomes false, runs leave animation then hides
  3. Initial state: No animation on page load (prevents FOUC)
  4. Interruption: Smoothly handles toggling during animation

Learn More

  • Display & Binding - Bind data to elements
  • Events - Handle user interactions that trigger style changes
  • Teleport - Render content in different DOM locations (modals)