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:
<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:
<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:
<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:
<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:
<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:
<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:
<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
Active Navigation Links
<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
<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
<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
<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
<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
<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:
<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:
<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:
<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:
<!-- 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:
<div data-show="$visible"
data-transition__opacity__duration__500__origin__top__delay__100>
Customized transition
</div>Available Helper Modifiers
| Modifier | Description | Default |
|---|---|---|
__opacity | Include opacity transition | true |
__scale | Include scale transition | true |
__scale__[value] | Custom scale value (0-100) | 95 |
__duration__[ms] | Duration in milliseconds | 150 enter, 75 leave |
__delay__[ms] | Delay before starting | 0 |
__origin__[pos] | Transform origin | center |
__in | Apply only on enter (show) | - |
__out | Apply 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:
<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
| Stage | When Applied | Purpose |
|---|---|---|
enter | During entire enter animation | Transition properties |
enter-start | Before element appears | Initial state (hidden) |
enter-end | After first frame | Final state (visible) |
leave | During entire leave animation | Transition properties |
leave-start | Before element disappears | Initial state (visible) |
leave-end | After first frame | Final state (hidden) |
Minimal Class Mode
You don't need all 6 stages. Often just enter and leave are enough:
<div data-show="$visible"
data-transition:enter="transition-opacity duration-300"
data-transition:leave="transition-opacity duration-200">
Simple fade
</div>Slide Animations
<!-- 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:
<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
<!-- 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
<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:
<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
| Modifier | Description | Default |
|---|---|---|
__collapse | Enable collapse mode | - |
__duration__[ms] | Animation duration | 250ms |
__min__[px] | Minimum collapsed height | 0 (fully hidden) |
Transition + Show Integration
All transition modes work seamlessly with data-show. The transition handles:
- Enter: When
data-showbecomestrue, runs enter animation then displays - Leave: When
data-showbecomesfalse, runs leave animation then hides - Initial state: No animation on page load (prevents FOUC)
- 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)
- Datastar Documentation - Complete reference for styling attributes
