Svelte Shadcn Blocks

repository·master·Indexed 19 days ago

https://github.com/sikandarjodd/cnblocks

A collection of over 150 production-ready, responsive UI blocks for Svelte 5 and SvelteKit, built on shadcn-svelte and Tailwind CSS. It features three visual styles: Normal (bold marketing), Mist (minimal documentation), and Veil (modern SaaS). The library provides a registry of components including hero sections, pricing tables, and contact forms, optimized for Svelte 5 and Tailwind CSS v4.

Tokens
38.5K
Snippets
141
Records
161
Agent score
62%

What's inside @sv/cnblocks

  1. Choose between universal and server-only load functions

    master

    SvelteKit provides two types of load functions. Choosing the correct one is critical for security and data serialization.

    +page.server.ts (Server-only)

    Use this when you need to:

    • Access secrets or credentials (e.g., API keys from $env/static/private).
    • Connect to a database.
    • Call server-only APIs.
    • Execute sensitive business logic.

    +page.js (Universal)

    Use this when you need to:

    • Access public APIs.
    • Return non-serializable data (e.g., functions, classes, or DOMParser).
    • Benefit from client-side caching.

    Warning: Never use +page.js for secrets, as this code runs in the browser and will expose your credentials.

    // +page.server.ts - CORRECT for secrets
    import { STRIPE_SECRET_KEY } from "$env/static/private";
    
    export const load = async ({ fetch }) => {
      const response = await fetch("https://api.stripe.com/charges", {
        headers: { Authorization: `Bearer ${STRIPE_SECRET_KEY}` },
      });
      return { charges: await response.json() };
    };
  2. Implement the Callback Props pattern for component communication

    master

    In Svelte 5, createEventDispatcher is deprecated. Instead, use callback props to communicate from child components to parents. This involves receiving a function via $props() and calling it when an event occurs.

    Basic Pattern

    1. Child: Destructure the callback (e.g., onclick) from $props().
    2. Child: Call the callback inside an event handler, optionally passing data.
    3. Parent: Pass a function to the component prop.

    Multiple Callbacks

    You can define multiple specific callbacks like onconfirm, oncancel, or onclose to handle different component actions.

    <!-- Button.svelte (Child) -->
    <script>
      let { onclick } = $props();
    </script>
    <button onclick={() => onclick?.({ timestamp: Date.now() })}>Click</button>
    
    <!-- Parent.svelte -->
    <Button onclick={(data) => console.log(data)} />
  3. Use the Context API for dependency injection

    master

    The Context API allows you to share data down the component tree.

    Rules of Usage

    • Synchronous Initialization: setContext and getContext must be called synchronously during component initialization. Never call setContext inside an $effect or a callback.
    • Reactivity: To make context reactive, pass a $state object or an object containing getters/setters.
    • Keys: Avoid using generic strings for keys to prevent collisions. Use a Symbol, a unique string (e.g., "myapp:user"), or an object key.

    Checking for Context

    Use hasContext(key) to check if a context exists before attempting to retrieve it with getContext(key) to avoid errors.

    <!-- Reactive Context Example -->
    <script>
      import { setContext } from 'svelte';
      let theme = $state('light');
    
      setContext('theme', {
        get current() { return theme; },
        toggle() { theme = theme === 'light' ? 'dark' : 'light'; }
      });
    </script>
  4. Use Union and Discriminated Union types for props

    master

    You can use TypeScript union types to restrict props to specific allowed values or to create complex conditional prop structures.

    Union Type Props

    Use string literal unions to restrict a prop to a specific set of strings (e.g., "primary" | "secondary" | "danger").

    Discriminated Union Props

    For components that change their entire interface based on a specific prop, use a discriminated union. This allows TypeScript to narrow the available props based on a 'type' or 'kind' field, ensuring that if type is 'link', the href property is required, but if type is 'button', an onclick handler is required instead.

    <script lang="ts">
      type Props = 
        | { type: "link"; href: string; children: import('svelte').Snippet } 
        | { type: "button"; onclick: () => void; children: import('svelte').Snippet };
    
      let props: Props = $props();
    </script>
    
    {#if props.type === "link"}
      <a href={props.href}>{@render props.children()}</a>
    {:else}
      <button onclick={props.onclick}>{@render props.children()}</button>
    {/if}
  5. Manage reactive state with Svelte 5 Runes

    master

    In Svelte 5, reactivity is managed through runes. Use $state to declare reactive variables and $derived to create computed values that automatically update when their dependencies change. Avoid using standard let declarations for values that need to trigger UI updates, as they are not reactive by default.

    Key runes include:

    • $state(initialValue): Declares reactive state.
    • $derived(expression): Declares a computed value.
    • $effect(fn): Runs side effects when dependencies change.
    • $props(): Accesses component properties.
    • $bindable(): Marks a prop as allowing two-way binding.
    • $inspect(value): Used for debugging reactive state.
    <script>
      let count = $state(0); // Reactive state
      let doubled = $derived(count * 2); // Computed value
    </script>
  6. Use Universal Reactivity for shared state in Svelte 5

    master

    Svelte 5 allows you to define shared, reactive state outside of components using runes in .svelte.js or .svelte.ts files. This enables a centralized state management pattern that can be imported into any component.

    Implementation Patterns

    1. Shared Counter (Simple State)

    Export a $state object and associated functions to manipulate it.

    2. Object State with Getters

    To expose derived properties or controlled access to state, use a plain object with get and set accessors that wrap a private $state object.

    3. Reactive Class Pattern

    Encapsulate complex state and logic within a class using $state for properties. Use getters for derived logic (e.g., filtering lists).

    Important Constraints

    • File Extensions: Shared state files MUST use .svelte.js or .svelte.ts.
    • Module Scope: Do not use $derived in the module scope; use standard JavaScript get accessors instead.
    • SSR: Avoid initializing browser-only state at the module level to prevent issues during Server-Side Rendering.
    // counter.svelte.ts
    export const counter = $state({ count: 0 });
    
    export function increment() {
      counter.count++;
    }
    
    // user.svelte.ts
    const state = $state({ firstName: "", lastName: "", email: "" });
    export const user = {
      get firstName() { return state.firstName; },
      set firstName(v) { state.firstName = v; },
      get fullName() { return `${state.firstName} ${state.lastName}`; }
    };
    
    // todo.svelte.ts
    class TodoStore {
      items = $state([]);
      filter = $state("all");
      get filtered() {
        return this.items.filter(t => t.done);
      }
    }
    export const todos = new TodoStore();
  7. How Svelte 5 Runes work together

    master

    Svelte 5 uses a 'Runes' system to manage reactivity explicitly.

    • $state defines the source of truth.
    • $derived creates reactive transformations of that state.
    • $props and $bindable manage how state flows between components.
    • $effect handles the bridge between reactive state and the outside world (DOM, APIs, etc.).
    • untrack allows you to read reactive values inside an effect without creating a dependency on them.
    <script>
      import { untrack } from "svelte";
    
      let count = $state(0);
      let logCount = $state(0);
    
      $effect(() => {
        // This effect runs when 'count' changes,
        // but 'logCount' is ignored because of untrack().
        console.log(count, untrack(() => logCount));
      });
    </script>
  8. Compose a Card component

    master

    The Card component is composed of several sub-components that can be used to build structured content blocks. The exported components are:

    • Card (or Root): The main container.
    • CardHeader: Container for the title and description.
    • CardTitle: The heading element (supports level prop from 1-6).
    • CardDescription: Subtext for the header.
    • CardContent: The main body area.
    • CardFooter: The bottom area for actions or metadata.
    <Card>
      <CardHeader>
        <CardTitle level={2}>Title</CardTitle>
        <CardDescription>Description text</CardDescription>
      </CardHeader>
      <CardContent>
        Main content goes here.
      </CardContent>
      <CardFooter>
        Footer content
      </CardFooter>
    </Card>
  9. Replace slots with Snippets

    master

    Svelte 5 replaces the <slot /> syntax with snippets. Snippets allow you to pass chunks of UI as props. You receive them via $props() and render them using the {@render ...} tag. This provides better type safety and more flexible composition than traditional slots.

    <script>
      // Snippets are received as props
      let { children, header } = $props();
    </script>
    
    <!-- Render the header snippet if it exists -->
    {@render header?.()}
    
    <!-- Render the default children snippet -->
    {@render children()}
  10. Prevent SSR state leakage and data contamination

    master

    In SvelteKit, server-side code is shared across requests. Using module-level variables or global stores to hold user-specific data will cause data to leak between different users.

    The Golden Rules of SSR Safety

    1. No module-level let variables for user data: Variables declared outside the load function in +page.server.ts are singletons shared by all users.
    2. No global $state for user data: Global stores initialized on the server will persist across requests.
    3. Use event.locals: Pass user-specific data from hooks.server.ts into event.locals, then return it from your load functions.
    4. Use Context for Component Trees: For deep component trees, use setContext in a layout to provide data safely.
    5. Client-only state: If you must use a global store, ensure it only initializes or operates when browser is true.
    // CORRECT: Use locals to avoid sharing state between users
    // hooks.server.ts
    export const handle = async ({ event, resolve }) => {
      event.locals.user = await authenticate(event);
      return resolve(event);
    };
    
    // +page.server.ts
    export const load = async ({ locals }) => {
      return { user: locals.user }; // Each request gets its own data
    };
  11. Configure Vitest for Svelte 5 component testing

    master

    Testing Svelte 5 components requires specific Vitest configurations depending on your preferred environment.

    Provides a more realistic testing environment by running tests in a real browser via Playwright.

    Option 2: JSDOM

    Uses a simulated DOM environment. This is faster but less accurate for complex browser behaviors.

    Common Testing Tasks

    • Props: Pass props using the props option in render.
    • Callbacks: Use vi.fn() to create mock functions and verify they are called.
    • Async: Use waitFor from @testing-library/svelte to handle components that load data asynchronously.
    // vitest.config.ts (Browser Mode)
    import { defineConfig } from "vitest/config";
    import { svelte } from "@sveltejs/vite-plugin-svelte";
    
    export default defineConfig({
      plugins: [svelte()],
      test: {
        browser: {
          enabled: true,
          provider: "playwright",
          name: "chromium",
        },
      },
    });
    
    // Basic Component Test Example
    import { render, screen, fireEvent } from "@testing-library/svelte";
    import { expect, test } from "vitest";
    import Counter from "./Counter.svelte";
    
    test("increments count when clicked", async () => {
      render(Counter);
      const button = screen.getByRole("button");
      await fireEvent.click(button);
      expect(button).toHaveTextContent("Count: 1");
    });
  12. Type props in Svelte 5 using $props()

    master

    In Svelte 5, the $props() rune requires specific TypeScript patterns. While inline typing is possible, using an interface is the recommended approach for clarity and maintainability.

    Basic Props Typing

    Use an interface to define the shape of your props, including optional properties with ? and default values during destructuring.

    Children and Snippets Typing

    To type Svelte snippets, import the Snippet type from svelte. Snippets can be required or optional, and can accept parameters defined as a tuple.

    Callback Props Typing

    Define callback functions within your props interface using standard TypeScript function signatures (e.g., (value: string) => void).

    Rest Props with HTML Attributes

    To allow a component to accept all standard HTML attributes (like class, id, etc.), extend the appropriate type from svelte/elements (e.g., HTMLButtonAttributes). Use the spread operator (...rest) to pass these attributes to the underlying element.

    Input Element Props

    When creating custom input components, extend HTMLInputAttributes. It is common to use Omit<HTMLInputAttributes, "value"> if you intend to redefine the value prop (for example, to make it $bindable).

    <script lang="ts">
      interface Props {
        name: string;
        count?: number;
        disabled?: boolean;
      }
    
      let { name, count = 0, disabled = false }: Props = $props();
    </script>