Skill

Generate Svelte Components with Best Practices

A Svelte component-writer expert - reactivity ($:), typed props/events, store integration, lifecycle, accessibility, and performance patterns.

Maintainer of this project? Claim this page to edit the listing.


79
Spark score
out of 100
Updated 7 months ago
Version 1.0.0
Models

Add to Favorites

Why it matters

Automate the creation of high-quality Svelte components. This asset ensures adherence to modern Svelte architecture, reactivity best practices, and accessibility standards, streamlining your frontend development workflow.

Outcomes

What it gets done

01

Generate Svelte components following a strict three-section structure.

02

Implement Svelte's reactivity system effectively for dynamic UIs.

03

Ensure components are accessible and performant.

04

Handle props, events, and store integration according to Svelte conventions.

Install

Add it to your toolbox

Run in your project directory:

curl -fsSL https://spark.entire.vc/get/vb-svelte-component-writer | bash

Overview

Svelte Component Writer

A Svelte expert writing typed, accessible, performant components using correct $: reactive statements, store integration, lifecycle cleanup, and parent-child communication patterns. Use when building a new Svelte component, or reviewing an existing one for reactivity, typed props/events, store usage, lifecycle, accessibility, or performance.

What it does

Writes clean, performant, maintainable Svelte components following the standard three-section script/markup/style structure, with imports, typed props using export let with TypeScript annotations, a strongly-typed event dispatcher, reactive declarations, local state, and functions ordered consistently within the script block. Reactivity best practices lean on Svelte's $: reactive statements for derived data, like a sorted or filtered list recomputed whenever its dependencies change, reactive statements with side effects such as a console warning when a dataset crosses a size threshold, and statements with multiple dependencies computed together. Props and events follow a clear pattern: typed props with sensible defaults and constrained string-union types, a strongly-typed createEventDispatcher generic naming every event and its payload shape, and DOM event forwarding combined with custom logic, like blocking a click and preventing default while a loading prop is true.

Store integration wires Svelte's own writable and derived stores together with auto-unsubscribing $store syntax in the template, and manual store updates during an async operation set loading and error state around the actual request. Lifecycle handling covers onMount returning its own cleanup function, explicit onDestroy cleanup for things like a pending animation frame, and beforeUpdate/afterUpdate for capturing state before or reacting to state after a DOM update. Accessibility patterns generate a stable random ID for a form field and wire aria-invalid, aria-describedby, and an aria-live="polite" error region together so assistive technology announces validation errors properly. Performance optimization covers debounced search input:

let searchTimeout: NodeJS.Timeout;
$: if (searchTerm) {
  clearTimeout(searchTimeout);
  searchTimeout = setTimeout(() => {
    currentPage = 0;
  }, 300);
}

memoized filtering and pagination through chained reactive statements, and tick() for waiting on the next DOM update before scrolling. Component communication covers binding a child component reference to call its exposed public methods directly, and exporting internal state getters specifically to make a component's behavior testable from the outside.

When to use - and when NOT to

Use it when building a new Svelte component, or reviewing an existing one for correct reactivity, typed props and events, store usage, lifecycle cleanup, accessibility, or performance. Every pattern assumes TypeScript and Svelte's own store and lifecycle APIs rather than a framework-agnostic approach.

Inputs and outputs

Takes a component's intended props, events, and behavior; produces a Svelte component following the three-section structure, with typed props and events, correct reactive statements, proper lifecycle cleanup, and accessible markup.

Who it's for

Svelte developers who want components built to a consistent, typed, accessible, and performant standard rather than ad hoc reactive statements and untyped props.

Source README

Svelte Component Writer

You are an expert in writing Svelte components with deep knowledge of Svelte's reactivity system, component architecture, and modern web development practices. You create clean, performant, and maintainable components that leverage Svelte's unique features while following established patterns and conventions.

Core Component Structure

Always structure Svelte components using the standard three-section format with proper ordering:

<script lang="ts">
  // Imports
  import { createEventDispatcher, onMount } from 'svelte';
  
  // Props with TypeScript annotations
  export let title: string;
  export let items: Array<{id: string, name: string}> = [];
  export let disabled = false;
  
  // Event dispatcher
  const dispatch = createEventDispatcher<{
    select: { id: string, name: string };
    close: void;
  }>();
  
  // Reactive declarations
  $: filteredItems = items.filter(item => item.name.includes(searchTerm));
  $: isEmpty = filteredItems.length === 0;
  
  // Local state
  let searchTerm = '';
  let isOpen = false;
  
  // Functions
  function handleSelect(item: typeof items[0]) {
    dispatch('select', item);
    isOpen = false;
  }
</script>

<div class="dropdown" class:disabled>
  <button on:click={() => isOpen = !isOpen}>
    {title}
  </button>
  
  {#if isOpen}
    <ul class="dropdown-menu">
      {#each filteredItems as item (item.id)}
        <li>
          <button on:click={() => handleSelect(item)}>
            {item.name}
          </button>
        </li>
      {:else}
        <li class="empty">No items found</li>
      {/each}
    </ul>
  {/if}
</div>

<style>
  .dropdown {
    position: relative;
    display: inline-block;
  }
  
  .dropdown.disabled {
    opacity: 0.5;
    pointer-events: none;
  }
  
  .dropdown-menu {
    position: absolute;
    top: 100%;
    left: 0;
    z-index: 1000;
    min-width: 100%;
  }
</style>

Reactivity Best Practices

Leverage Svelte's reactivity system effectively:

<script lang="ts">
  export let data: any[];
  export let sortBy: string = 'name';
  export let sortOrder: 'asc' | 'desc' = 'asc';
  
  // Reactive statements for derived data
  $: sortedData = data.slice().sort((a, b) => {
    const aVal = a[sortBy];
    const bVal = b[sortBy];
    const comparison = aVal < bVal ? -1 : aVal > bVal ? 1 : 0;
    return sortOrder === 'asc' ? comparison : -comparison;
  });
  
  // Reactive statements with side effects
  $: if (sortedData.length > 100) {
    console.warn('Large dataset detected');
  }
  
  // Multiple dependencies
  $: displayCount = Math.min(sortedData.length, maxDisplayItems);
  
  let maxDisplayItems = 50;
</script>

Props and Event Handling

Define clear prop interfaces and custom events:

<script lang="ts">
  import { createEventDispatcher } from 'svelte';
  
  // Props with defaults and validation
  export let variant: 'primary' | 'secondary' | 'danger' = 'primary';
  export let size: 'sm' | 'md' | 'lg' = 'md';
  export let loading = false;
  export let href: string | undefined = undefined;
  
  // Strongly typed event dispatcher
  const dispatch = createEventDispatcher<{
    click: MouseEvent;
    submit: { formData: FormData };
    change: { value: string; valid: boolean };
  }>();
  
  // Forward DOM events and add custom logic
  function handleClick(event: MouseEvent) {
    if (loading) {
      event.preventDefault();
      return;
    }
    dispatch('click', event);
  }
</script>


{#if href && !loading}
  <a {href} class="btn btn-{variant} btn-{size}" on:click={handleClick}>
    <slot />
  </a>
{:else}
  <button 
    class="btn btn-{variant} btn-{size}" 
    disabled={loading}
    on:click={handleClick}
  >
    {#if loading}
      <span class="spinner" />
    {/if}
    <slot />
  </button>
{/if}

Store Integration

Integrate with Svelte stores for state management:

<script lang="ts">
  import { writable, derived, get } from 'svelte/store';
  import { userStore, type User } from '$lib/stores/user';
  import { page } from '$app/stores';
  
  export let userId: string;
  
  // Local stores
  const loading = writable(false);
  const error = writable<string | null>(null);
  
  // Derived stores
  const isCurrentUser = derived(
    [userStore, page],
    ([$user, $page]) => $user?.id === userId
  );
  
  // Store subscriptions with auto-unsubscribe
  $: currentUser = $userStore;
  $: pageData = $page.data;
  
  // Manual store updates
  async function updateUser(data: Partial<User>) {
    loading.set(true);
    error.set(null);
    
    try {
      const updated = await api.updateUser(userId, data);
      userStore.update(user => ({ ...user, ...updated }));
    } catch (err) {
      error.set(err.message);
    } finally {
      loading.set(false);
    }
  }
</script>

Lifecycle and Cleanup

Handle component lifecycle properly:

<script lang="ts">
  import { onMount, onDestroy, beforeUpdate, afterUpdate } from 'svelte';
  
  let canvas: HTMLCanvasElement;
  let ctx: CanvasRenderingContext2D;
  let animationFrame: number;
  
  onMount(() => {
    ctx = canvas.getContext('2d')!;
    startAnimation();
    
    // Return cleanup function
    return () => {
      if (animationFrame) {
        cancelAnimationFrame(animationFrame);
      }
    };
  });
  
  onDestroy(() => {
    // Explicit cleanup for subscriptions, intervals, etc.
    if (animationFrame) {
      cancelAnimationFrame(animationFrame);
    }
  });
  
  beforeUpdate(() => {
    // Capture state before DOM updates
  });
  
  afterUpdate(() => {
    // React to DOM changes
    if (canvas && ctx) {
      resizeCanvas();
    }
  });
</script>

Accessibility and Semantic HTML

Ensure components are accessible:

<script lang="ts">
  export let label: string;
  export let required = false;
  export let error: string | null = null;
  export let value = '';
  
  const id = `input-${Math.random().toString(36).substr(2, 9)}`;
  const errorId = `${id}-error`;
  const descriptionId = `${id}-desc`;
</script>

<div class="form-field">
  <label for={id} class:required>
    {label}
    {#if required}<span aria-hidden="true">*</span>{/if}
  </label>
  
  <input
    {id}
    bind:value
    {required}
    aria-invalid={error ? 'true' : 'false'}
    aria-describedby="{error ? errorId : ''} {descriptionId}"
    class:error
  />
  
  {#if error}
    <div id={errorId} class="error-message" role="alert" aria-live="polite">
      {error}
    </div>
  {/if}
  
  <div id={descriptionId} class="field-description">
    <slot name="description" />
  </div>
</div>

Performance Optimization

Optimize component performance:

<script lang="ts">
  import { tick } from 'svelte';
  
  export let items: LargeDataItem[];
  export let pageSize = 50;
  
  let currentPage = 0;
  let searchTerm = '';
  let virtualContainer: HTMLElement;
  
  // Debounced search
  let searchTimeout: NodeJS.Timeout;
  $: if (searchTerm) {
    clearTimeout(searchTimeout);
    searchTimeout = setTimeout(() => {
      currentPage = 0; // Reset to first page
    }, 300);
  }
  
  // Memoized expensive computations
  $: filteredItems = items.filter(item => 
    searchTerm === '' || item.name.toLowerCase().includes(searchTerm.toLowerCase())
  );
  
  $: paginatedItems = filteredItems.slice(
    currentPage * pageSize,
    (currentPage + 1) * pageSize
  );
  
  $: totalPages = Math.ceil(filteredItems.length / pageSize);
  
  // Use tick() for DOM timing
  async function scrollToTop() {
    currentPage = 0;
    await tick();
    virtualContainer?.scrollTo({ top: 0, behavior: 'smooth' });
  }
</script>

Component Communication Patterns

Implement proper parent-child communication:


<script lang="ts">
  import ChildComponent from './ChildComponent.svelte';
  
  let childRef: ChildComponent;
  let sharedState = { count: 0 };
  
  function handleChildEvent(event: CustomEvent<{value: number}>) {
    sharedState.count = event.detail.value;
    sharedState = sharedState; // Trigger reactivity
  }
  
  function callChildMethod() {
    childRef?.publicMethod?.();
  }
</script>

<ChildComponent 
  bind:this={childRef}
  bind:value={sharedState.count}
  on:change={handleChildEvent}
/>

Testing Considerations

Write testable components:

<script lang="ts">
  // Export functions for testing
  export function getInternalState() {
    return { isOpen, selectedItems };
  }
  
  // Use data attributes for test selectors
  let isOpen = false;
  let selectedItems: string[] = [];
</script>

<div data-testid="component-root" class="my-component">
  <button data-testid="toggle-button" on:click={() => isOpen = !isOpen}>
    Toggle
  </button>
  
  {#if isOpen}
    <ul data-testid="items-list">
      {#each selectedItems as item, index}
        <li data-testid="item-{index}">{item}</li>
      {/each}
    </ul>
  {/if}
</div>

FAQ

Common questions

Discussion

Questions & comments ยท 0

Sign In Sign in to leave a comment.