Skip to content
ExponaBLOG
SVELTE & SVELTEKIT · DAY 096 / 100

Svelte class and style directives in Svelte & SvelteKit

Dynamic styling made clean.

Expona Admin6 min read

Introduction

When you're building interactive UIs in Svelte, you'll quickly find that managing classes and styles dynamically is one of the most frequent tasks you'll encounter. The Svelte class and style directives in Svelte & SvelteKit give you a declarative way to apply conditional styling without resorting to JavaScript string concatenation or complex ternary expressions. This approach keeps your markup clean and your component logic straightforward, which matters when you're maintaining production code at scale.

We've worked through plenty of real-world scenarios where reactive styling is critical—user interaction feedback, form validation states, theme switching, and dynamic layout adjustments. Let's walk through how to use these directives effectively and avoid the pitfalls that can creep into your codebase.

The class directive: class:name={condition}

Svelte's class directive lets you bind a CSS class to a boolean expression. This is far cleaner than building class strings in JavaScript or using utility libraries. The syntax is straightforward: class:active={isActive} applies the active class whenever isActive is true.

Here's a practical example from a component that toggles between editing and viewing modes:

typescript
<script>
  let isEditing = false;

  function toggleEditMode() {
    isEditing = !isEditing;
  }
</script>

<button on:click={toggleEditMode}>
  {isEditing ? 'Save' : 'Edit'}
</button>

<div class:editing={isEditing}>
  <p>Content here</p>
</div>

<style>
  div.editing {
    border: 2px solid blue;
    background-color: #f0f4ff;
  }

  div :not(.editing) {
    border: 1px solid #ddd;
  }
</style>

This pattern is reactive by default. When isEditing changes, Svelte automatically adds or removes the editing class. You can also stack multiple class directives on the same element:

typescript
<div 
  class:active={isActive}
  class:disabled={isDisabled}
  class:loading={isLoading}
>
  Status indicator
</div>

One powerful pattern is using a shorthand syntax when the condition variable has the same name as the class. Instead of class:active={active}, you can write just class:active. This reduces noise in your templates.

The style directive: style:property={value}

For styles that can't be expressed as static classes—think dynamic colors, dimensions, or positioning—the style directive gives you a reactive escape hatch. The syntax mirrors the class directive: style:color={dynamicColor} applies the color property directly to the element.

Consider a theme switcher component where users can pick accent colors:

typescript
<script>
  let accentColor = '#007bff';
  let textSize = 16;

  const themeOptions = [
    { name: 'Blue', color: '#007bff' },
    { name: 'Green', color: '#28a745' },
    { name: 'Red', color: '#dc3545' }
  ];
</script>

<div>
  {#each themeOptions as theme (theme.color)}
    <button 
      on:click={() => accentColor = theme.color}
    >
      {theme.name}
    </button>
  {/each}
</div>

<div 
  style:color={accentColor}
  style:font-size={textSize + 'px'}
>
  This text uses dynamic styling
</div>

Style directives are applied directly to the DOM element, so they take precedence over scoped styles in the <style> block. This makes them ideal for truly dynamic values that you can't know at compile time. However, prefer scoped styles for values that are predetermined.

Combining class directives with scoped styles

The real power emerges when you combine reactive class directives with Svelte's scoped styles. This approach keeps your styling rules organized and performant while maintaining reactive behavior. Your CSS stays in the <style> block where it belongs, and the template just toggles the classes.

Let's look at a form component that validates fields in real time:

typescript
<script>
  let email = '';
  let password = '';
  let submitted = false;

  function isValidEmail(e) {
    return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(e);
  }

  function handleSubmit() {
    submitted = true;
    if (isValidEmail(email) && password.length >= 8) {
      console.log('Form valid, submit');
    }
  }
</script>

<form on:submit|preventDefault={handleSubmit}>
  <div class="form-group">
    <label for="email">Email</label>
    <input
      id="email"
      type="email"
      bind:value={email}
      class:error={submitted && !isValidEmail(email)}
      class:success={submitted && isValidEmail(email)}
    />
  </div>

  <div class="form-group">
    <label for="password">Password</label>
    <input
      id="password"
      type="password"
      bind:value={password}
      class:error={submitted && password.length < 8}
      class:success={submitted && password.length >= 8}
    />
  </div>

  <button type="submit">Submit</button>
</form>

<style>
  .form-group {
    margin-bottom: 1rem;
    display: flex;
    flex-direction: column;
  }

  label {
    font-weight: 600;
    margin-bottom: 0.25rem;
  }

  input {
    padding: 0.5rem;
    border: 2px solid #ddd;
    border-radius: 4px;
    transition: border-color 0.2s, background-color 0.2s;
  }

  input:focus {
    outline: none;
    border-color: #007bff;
  }

  input.error {
    border-color: #dc3545;
    background-color: #fff5f5;
  }

  input.success {
    border-color: #28a745;
    background-color: #f5fff5;
  }

  button {
    padding: 0.75rem 1.5rem;
    background-color: #007bff;
    color: white;
    border: none;
    border-radius: 4px;
    cursor: pointer;
    font-weight: 600;
  }

  button:hover {
    background-color: #0056b3;
  }
</style>

This approach has several benefits: your styles are scoped to the component, the class names act as semantic markers for validation states, and the logic remains in JavaScript where it's testable and maintainable. When the form validation logic changes, you update the condition in the template, not the CSS.

Avoid inline style strings

A common antipattern we see is building style strings dynamically in JavaScript and applying them via a general style attribute. This defeats Svelte's reactivity benefits and makes your code harder to read:

typescript
<!-- Avoid this -->
<script>
  let color = 'red';
  let size = 16;
  let computedStyle = `color: ${color}; font-size: ${size}px;`;
</script>

<div style={computedStyle}>
  Bad approach
</div>

Instead, use individual style directives for each property:

typescript
<!-- Do this instead -->
<script>
  let color = 'red';
  let size = 16;
</script>

<div style:color style:font-size={size + 'px'}>
  Good approach
</div>

The second approach is more readable, easier to debug, and Svelte can optimize it better. Each style property is explicitly tracked, and the compiler knows exactly which values affect the DOM.

Common mistakes

Mixing static and reactive classes: When you have both static classes and reactive ones, separate them clearly. Use class="container" class:active={isActive} rather than trying to build the whole string dynamically. Svelte merges them intelligently, but explicit code is easier to maintain.

Using style directives for everything: If a style value is static or if you have many related styles, a class is cleaner. Style directives shine for single, dynamic properties like colors or sizes. Don't reach for them when scoped CSS classes would be better.

Forgetting specificity issues: Inline styles have high specificity. If you're applying style:color={color} and wondering why your scoped CSS color rule isn't working, remember that inline styles win. Design your component's reactive behavior first, then add static styles that don't conflict.

Not considering performance: If a style directive updates on every keystroke or mouse move, you're creating a lot of DOM updates. Consider throttling or debouncing the reactive variable, or move the computation into a reactive variable that updates less frequently.

Key takeaways

  • Use class:name={condition} to reactively toggle CSS classes; this keeps styling rules in the style block where they belong.
  • Use style:property={value} for truly dynamic values that can't be expressed as static classes, like user-selected colors or computed dimensions.
  • Combine class directives with scoped styles for clean, maintainable, and performant reactive UI updates.
  • Avoid building style strings in JavaScript; individual style directives are more readable and better optimized by Svelte's compiler.
  • Pay attention to specificity: inline styles override scoped CSS, so plan your reactive styling strategy upfront.
  • Use the shorthand class:active syntax when the class name matches the variable name, reducing verbosity.
ShareXLinkedInReddit

Found this useful? Support the writing.

Buy me a coffee
Written by
Expona Admin
Site administrator

Comments

Loading comments…

Leave a comment

Comments are reviewed before they appear. Be kind and stay on topic.

Related articles