home / skills / windmill-labs / windmill / svelte-frontend

svelte-frontend skill

/.claude/skills/svelte-frontend

This skill helps you apply Svelte 5 best practices with Runes API for explicit reactivity, improving frontend code quality in Windmill projects.

npx playbooks add skill windmill-labs/windmill --skill svelte-frontend

Review the files below or copy the command above to add this skill to your agents.

Files (1)
SKILL.md
10.5 KB
---
name: svelte-frontend
description: Svelte coding guidelines for the Windmill frontend. MUST use when writing or modifying code in the frontend directory.
---

# Svelte 5 Best Practices

This guide outlines best practices for developing with Svelte 5, incorporating the new Runes API and other modern Svelte features. These rules MUST NOT be applied on svelte 4 files unless explicitly asked to do so.

## Reactivity with Runes

Svelte 5 introduces Runes for more explicit and flexible reactivity.

1.  **Embrace Runes for State Management**:
    *   Use `$state` for reactive local component state.
        ```svelte
        <script>
          let count = $state(0);

          function increment() {
            count += 1;
          }
        </script>

        <button onclick={increment}>
          Clicked {count} {count === 1 ? 'time' : 'times'}
        </button>
        ```
    *   Use `$derived` for computed values based on other reactive state.
        ```svelte
        <script>
          let count = $state(0);
          const doubled = $derived(count * 2);
        </script>

        <p>{count} * 2 = {doubled}</p>
        ```
    *   Use `$effect` for side effects that need to run when reactive values change (e.g., logging, manual DOM manipulation, data fetching). Remember `$effect` does not run on the server.
        ```svelte
        <script>
          let count = $state(0);

          $effect(() => {
            console.log('The count is now', count);
            if (count > 5) {
              alert('Count is too high!');
            }
          });
        </script>
        ```

2.  **Props with `$props`**:
    *   Declare component props using `$props()`. This offers better clarity and flexibility compared to `export let`.
        ```svelte
        <script>
          // ChildComponent.svelte
          let { name, age = $state(30) } = $props();
        </script>

        <p>Name: {name}</p>
        <p>Age: {age}</p>
        ```
    *   For bindable props, use `$bindable`.
        ```svelte
        <script>
          // MyInput.svelte
          let { value = $bindable() } = $props();
        </script>

        <input bind:value />
        ```

## Event Handling

*   **Use direct event attributes**: Svelte 5 moves away from `on:` directives for DOM events.
    *   **Do**: `<button onclick={handleClick}>...</button>`
    *   **Don't**: `<button on:click={handleClick}>...</button>`
*   **For component events, prefer callback props**: Instead of `createEventDispatcher`, pass functions as props.
    ```svelte
    <!-- Parent.svelte -->
    <script>
      import Child from './Child.svelte';
      let message = $state('');
      function handleChildEvent(detail) {
        message = detail;
      }
    </script>
    <Child onCustomEvent={handleChildEvent} />
    <p>Message from child: {message}</p>

    <!-- Child.svelte -->
    <script>
      let { onCustomEvent } = $props();
      function emitEvent() {
        onCustomEvent('Hello from child!');
      }
    </script>
    <button onclick={emitEvent}>Send Event</button>
    ```

## Snippets for Content Projection

*   **Use `{#snippet ...}` and `{@render ...}` instead of slots**: Snippets are more powerful and flexible.
    ```svelte
    <!-- Parent.svelte -->
    <script>
      import Card from './Card.svelte';
    </script>

    <Card>
      {#snippet title()}
        My Awesome Title
      {/snippet}
      {#snippet content()}
        <p>Some interesting content here.</p>
      {/snippet}
    </Card>

    <!-- Card.svelte -->
    <script>
      let { title, content } = $props();
    </script>

    <article>
      <header>{@render title()}</header>
      <div>{@render content()}</div>
    </article>
    ```
*   Default content is passed via the `children` prop (which is a snippet).
    ```svelte
    <!-- Wrapper.svelte -->
    <script>
      let { children } = $props();
    </script>
    <div>
      {@render children?.()}
    </div>
    ```

## Component Design

1.  **Create Small, Reusable Components**: Break down complex UIs into smaller, focused components. Each component should have a single responsibility. This also aids performance by limiting the scope of reactivity updates.
2.  **Descriptive Naming**: Use clear and descriptive names for variables, functions, and components.
3.  **Minimize Logic in Components**: Move complex business logic to utility functions or services. Keep components focused on presentation and interaction.

## State Management (Stores)

1.  **Segment Stores**: Avoid a single global store. Create multiple stores, each responsible for a specific piece of global state (e.g., `userStore.js`, `themeStore.js`). This can help limit reactivity updates to only the parts of the UI that depend on specific state segments.
2.  **Use Custom Stores for Complex Logic**: For stores with related methods, create custom stores.
    ```javascript
    // counterStore.js
    import { writable } from 'svelte/store';

    function createCounter() {
      const { subscribe, set, update } = writable(0);

      return {
        subscribe,
        increment: () => update(n => n + 1),
        decrement: () => update(n => n - 1),
        reset: () => set(0)
      };
    }
    export const counter = createCounter();
    ```
3.  **Use Context API for Localized State**: For state shared within a component subtree, consider Svelte's context API (`setContext`, `getContext`) instead of global stores when the state doesn't need to be truly global.

## Performance Optimizations (Svelte 5)

When generating Svelte 5 code, prioritize frontend performance by applying the following principles:

### General Svelte 5 Principles

-   **Leverage the Compiler:** Trust Svelte's compiler to generate optimized JavaScript. Avoid manual DOM manipulation (`document.querySelector`, etc.) unless absolutely necessary for integrating third-party libraries that lack Svelte adapters.
-   **Keep Components Small and Focused:** Reinforcing from Component Design, smaller components lead to less complex reactivity graphs and more targeted, efficient updates.

### Reactivity & State Management

-   **Optimize Computations with `$derived`:** Always use `$derived` for computed values that depend on other state. This ensures the computation only runs when its specific dependencies change, avoiding unnecessary work compared to recomputing derived values in `$effect` or less efficient methods.
-   **Minimize `$effect` Usage:** Use `$effect` sparingly and only for true side effects that interact with the outside world or non-Svelte state. Avoid putting complex logic or state updates *within* an `$effect` unless those updates are explicitly intended as a reaction to external changes or non-Svelte state. Excessive or complex effects can impact rendering performance.
-   **Structure State for Fine-Grained Updates:** Design your `$state` objects or variables such that updates affect only the necessary parts of the UI. Avoid putting too much unrelated state into a single large object that gets frequently updated, as this can potentially trigger broader updates than necessary. Consider normalizing complex, nested state.

### List Rendering (`{#each}`)

-   **Mandate `key` Attribute:** Always use a `key` attribute (`{#each items as item (item.id)}`) that refers to a unique, stable identifier for each item in a list. This is critical for allowing Svelte to efficiently update, reorder, add, or remove list items without destroying and re-creating unnecessary DOM elements and component instances.

### Component Loading & Bundling

-   **Implement Lazy Loading/Code Splitting:** For routes, components, or modules that are not immediately needed on page load, use dynamic imports (`import(...)`) to split the code bundle. SvelteKit handles this automatically for routes, but it can be applied manually to components using helper patterns if needed.
-   **Be Mindful of Third-Party Libraries:** When incorporating external libraries, import only the necessary functions or components to minimize the final bundle size. Prefer libraries designed to be tree-shakeable.

### Rendering & DOM

-   **Use CSS for Animations/Transitions:** Prefer CSS animations or transitions where possible for performance. Svelte's built-in `transition:` directive is also highly optimized and should be used for complex state-driven transitions, but simple cases can often use plain CSS.
-   **Optimize Image Loading:** Implement best practices for images: use optimized formats (WebP, AVIF), lazy loading (`loading="lazy"`), and responsive images (`<picture>`, `srcset`) to avoid loading unnecessarily large images.

### Server-Side Rendering (SSR) & Hydration

-   **Ensure SSR Compatibility:** Write components that can be rendered on the server for faster initial page loads. Avoid relying on browser-specific APIs (like `window` or `document`) in the main `<script>` context. If necessary, use `$effect` or check `if (browser)` inside effects to run browser-specific code only on the client.
-   **Minimize Work During Hydration:** Structure components and data fetching such that minimal complex setup or computation is required when the client-side Svelte code takes over from the server-rendered HTML. Heavy synchronous work during hydration can block the main thread.

## General Clean Code Practices

1.  **Organized File Structure**: Group related files together. A common structure:
    ```
    /src
    |-- /routes      // Page components (if using a router like SvelteKit)
    |-- /lib         // Utility functions, services, constants (SvelteKit often uses this)
    |   |-- /stores
    |   |-- /utils
    |   |-- /services
    |   |-- /components  // Reusable UI components
    |-- App.svelte
    |-- main.js (or main.ts)
    ```
2.  **Scoped Styles**: Keep CSS scoped to components to avoid unintended side effects and improve maintainability. Avoid `:global` where possible.
3.  **Immutability**: With Svelte 5 and `$state`, direct assignments to properties of `$state` objects (`obj.prop = value;`) are generally fine as Svelte's reactivity system handles updates. However, for non-rune state or when interacting with other systems, understanding and sometimes preferring immutable updates (creating new objects/arrays) can still be relevant.
4.  **Use `class:` and `style:` directives**: For dynamic classes and styles, use Svelte's built-in directives for cleaner templates and potentially optimized updates.
    ```svelte
    <script>
      let isActive = $state(true);
      let color = $state('blue');
    </script>

    <div class:active={isActive} style:color={color}>
      Hello
    </div>
    ```
5.  **Stay Updated**: Keep Svelte and its related packages up to date to benefit from the latest features, performance improvements, and security fixes.

Overview

This skill provides Svelte 5 coding guidelines tailored for the Windmill frontend and must be used when writing or modifying files in the frontend directory. It codifies Runes-based reactivity, event and snippet conventions, component design, store segmentation, and performance rules so code is consistent, fast, and SSR-friendly. Follow these rules for new Svelte 5 files; do not apply them to Svelte 4 files unless explicitly directed.

How this skill works

The skill inspects Svelte files for adherence to Svelte 5 idioms: use of $state, $derived, and $effect; $props and $bindable for props; direct DOM event attributes; snippet-based content projection; and list keys. It flags anti-patterns like excessive $effect logic, single monolithic stores, missing list keys, direct DOM manipulation in main script, and unscoped styles. It also suggests performance improvements such as lazy loading, optimized images, and splitting large components.

When to use it

  • When creating or updating any file in the frontend directory written for Svelte 5.
  • When introducing or refactoring component state, props, or events.
  • When designing global or localized state and deciding between stores and context.
  • Before committing performance-sensitive UI changes (lists, images, routes).
  • When ensuring components remain SSR-compatible and hydrate efficiently.

Best practices

  • Use $state for local reactive variables, $derived for computed values, and $effect only for true side effects.
  • Declare props with $props() and $bindable for bindable props; prefer callback props over createEventDispatcher for component events.
  • Split global state into focused stores; use context API for subtree-local state and custom stores for related methods.
  • Always provide stable key attributes when rendering lists with {#each} to avoid unnecessary DOM churn.
  • Favor small, single-responsibility components, lazy-load noncritical components, and prefer CSS or Svelte transitions for animations.

Example use cases

  • Convert a component’s local state to $state and compute values with $derived to reduce re-renders.
  • Replace slot-based content projection with {#snippet} and {@render} for richer parent-child composition.
  • Refactor a large monolithic store into multiple focused stores (userStore, themeStore) to limit reactivity scope.
  • Implement dynamic import for a rarely used admin panel component to reduce initial bundle size.
  • Add keys to an {#each} list that previously re-rendered items on every update.

FAQ

Are these rules mandatory for Svelte 4 files?

No. These guidelines apply to Svelte 5 files only. Do not change Svelte 4 files to Svelte 5 patterns unless explicitly requested.

When should I use $effect vs $derived?

Use $derived for computed values derived from reactive state; use $effect only for side effects that interact with the outside world or browser-only work, and keep its logic minimal.