npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

sveld

v0.32.5

Published

Generate TypeScript definitions and component documentation for your Svelte components.

Readme

sveld

NPM npm downloads to date

sveld generates TypeScript definitions and component documentation (Markdown/JSON) for Svelte components. It statically analyzes props, events, slots, and the rest. Add types with JSDoc when inference is not enough.

The goal is to get third-party Svelte libraries working with the Svelte Language Server and TypeScript with minimal effort from the author. Generated .d.ts files give you autocomplete in VS Code and other IDEs.

Carbon Components Svelte uses this library to auto-generate component types and API metadata.

sveld uses the Svelte 5 compiler to parse .svelte files. That single parse path powers docgen and TypeScript output for Svelte 3, Svelte 4, Svelte 5 without runes (export let, <slot>, $$restProps, …), and Svelte 5 Runes ($props(), $bindable(), {@render ...}, callback props such as onclick, …).

For lang="ts" components, sveld keeps source-level prop type annotations when it can, instead of forcing JSDoc. That covers legacy export let props, typed $props() destructuring, typed whole-object $props() captures, local interface/type declarations, and imported type references in emitted .d.ts files.

| Syntax mode | Supported | | :------------------- | :-------: | | Svelte 3 | ✓ | | Svelte 4 | ✓ | | Svelte 5 (non-Runes) | ✓ | | Svelte 5 Runes | ✓ |

Generated .d.ts files extend SvelteComponentTyped from svelte, so TypeScript and the Svelte Language Server work whether consumers use Svelte 3, Svelte 4, or Svelte 5.


From a Svelte component, sveld can infer basic prop types and emit definitions the Svelte Language Server understands:

Button.svelte

<script>
  export let type = "button";
  export let primary = false;
</script>

<button {...$$restProps} {type} class:primary on:click>
  <slot>Click me</slot>
</button>

The following generated .d.ts extends SvelteComponentTyped:

Button.svelte.d.ts

import { SvelteComponentTyped } from "svelte";
import type { SvelteHTMLElements } from "svelte/elements";

type $RestProps = SvelteHTMLElements["button"];

type $Props = {
  /**
   * @default "button"
   */
  type?: string;

  /**
   * @default false
   */
  primary?: boolean;

  [key: `data-${string}`]: unknown;
};

export type ButtonProps = Omit<$RestProps, keyof $Props> & $Props;

export default class Button extends SvelteComponentTyped<
  ButtonProps,
  { click: WindowEventMap["click"] },
  { default: Record<string, never> }
> {}

Inference only gets you so far. Use JSDoc to document prop, event, and slot types when you need more precision.

/** @type {"button" | "submit" | "reset"} */
export let type = "button";

/**
 * Set to `true` to use the primary variant
 */
export let primary = false;

With JSDoc, the output looks like this:

import type { SvelteHTMLElements } from "svelte/elements";

type $RestProps = SvelteHTMLElements["button"];

type $Props = {
  /**
   * @default "button"
   */
  type?: "button" | "submit" | "reset";

  /**
   * Set to `true` to use the primary variant
   * @default false
   */
  primary?: boolean;
};

export type ButtonProps = Omit<$RestProps, keyof $Props> & $Props;

export default class Button extends SvelteComponentTyped<
  ButtonProps,
  { click: WindowEventMap["click"] },
  { default: Record<string, never> }
> {}

Table of Contents

Approach

sveld uses the Svelte compiler to statically analyze exported components and emit docs for consumers.

It extracts:

  • props
  • slots
  • forwarded events
  • dispatched events
  • context (setContext/getContext)
  • $$restProps

When inference fails, props fall back to any rather than guessing wrong. Authors can tighten types with JSDoc. Comments are optional from the compiler's point of view, so plain JavaScript components still parse.

When both TypeScript syntax and JSDoc are present, sveld resolves prop types in this order:

  1. explicit TypeScript annotation
  2. explicit JSDoc annotation
  3. initializer inference
  4. any

sveld stays AST-only. It copies imported and local type text into generated .d.ts output but does not run project-wide semantic resolution with the TypeScript compiler. Opaque imported whole-object $props() types can therefore stay in declarations without being fully expanded into JSON metadata.

Usage

Installation

Install sveld as a development dependency.

# npm
npm i -D sveld

# pnpm
pnpm i -D sveld

# Bun
bun i -D sveld

# Yarn
yarn add -D sveld

Vite

Import and add sveld as a plugin to your vite.config.ts. The plugin only runs during vite build (not the dev server).

// vite.config.ts
import { svelte } from "@sveltejs/vite-plugin-svelte";
import sveld from "sveld";
import { defineConfig } from "vite";

export default defineConfig({
  plugins: [svelte(), sveld()],
});

Since Vite uses Rollup for production builds, the same plugin works in Rollup configs.

By default, sveld will use the "svelte" field from your package.json to determine the entry point. You can override this by specifying an explicit entry option:

sveld({
  entry: "src/index.js",
});

When building the library, TypeScript definitions are emitted to the types folder by default.

Customize the output folder using the typesOptions.outDir option. Use typesOptions.printWidth to control Prettier wrapping for generated .d.ts files. The default is 80.

The following example emits the output to the dist folder:

sveld({
+  typesOptions: {
+    outDir: 'dist',
+    printWidth: 80
+  }
})

CLI

The CLI uses the "svelte" field from your package.json as the entry point:

npx sveld

Generate documentation in JSON and/or Markdown formats using the following flags:

npx sveld --json --markdown

Node.js

You can also call sveld from Node.js. The package is ESM-only; require("sveld") does not work. Use import or dynamic import().

If no input is specified, sveld will infer the entry point based on the package.json#svelte field.

import { sveld } from "sveld";
import pkg from "./package.json" with { type: "json" };

sveld({
  input: "./src/index.js",
  glob: true,
  markdown: true,
  markdownOptions: {
    onAppend: (type, document, components) => {
      if (type === "h1")
        document.append(
          "quote",
          `${components.size} components exported from ${pkg.name}@${pkg.version}.`,
        );
    },
  },
  json: true,
  jsonOptions: {
    outFile: "docs/src/COMPONENT_API.json",
  },
});

jsonOptions.outDir

With json: true, sveld writes COMPONENT_API.json at the project root. The file documents all components.

Use the jsonOptions.outDir option to specify the folder for individual JSON files to be emitted.

sveld({
  json: true,
  jsonOptions: {
    // an individual JSON file will be generated for each component API
    // e.g. "docs/Button.api.json"
    outDir: "docs",
  },
});

Publishing to NPM

TypeScript definitions land in the types folder by default. Include that folder in package.json when you publish to npm.

{
  "svelte": "./src/index.js",
  "main": "./lib/index.mjs",
+ "types": "./types/index.d.ts",
  "files": [
    "src",
    "lib",
+   "types",
  ]
}

Available Options

Plugin Options

  • entry (string, optional): Specify the entry point to uncompiled Svelte source. If not provided, sveld will use the "svelte" field from package.json.
  • glob (boolean, optional): Enable glob mode to analyze all *.svelte files.
  • types (boolean, optional, default: true): Generate TypeScript definitions.
  • typesOptions (object, optional): Options for TypeScript definition generation, including outDir, preamble, and printWidth.
  • json (boolean, optional): Generate component documentation in JSON format.
  • jsonOptions (object, optional): Options for JSON output.
  • markdown (boolean, optional): Generate component documentation in Markdown format.
  • markdownOptions (object, optional): Options for Markdown output.

By default, only TypeScript definitions are generated.

To generate documentation in Markdown and JSON formats, set markdown and json to true.

sveld({
+  markdown: true,
+  json: true,
})

JSON Output

When json: true is enabled, sveld emits a COMPONENT_API.json file with schema and generator metadata plus the parsed component API.

The JSON Schema lives on GitHub (path to file, raw URL). Use it to validate generated COMPONENT_API.json files. Optional fields may be missing when the parser has no stable source for that metadata.

interface ComponentApiJson {
  schemaVersion: 1;
  generator: {
    name: string;
    version: string;
    svelteVersion: string;
  };
  total: number;
  components: ComponentDocApi[];
}

interface SourceRange {
  start: SourcePosition;
  end: SourcePosition;
}

interface SourcePosition {
  line: number;
  column: number;
}

interface ComponentDocApi {
  moduleName: string;
  filePath: string;
  source?: SourceRange;
  syntaxMode: "legacy" | "runes";
  scriptLanguage?: "js" | "ts";
  props: ComponentProp[];
  moduleExports: ComponentProp[];
  slots: ComponentSlot[];
  events: ComponentEvent[];
  typedefs: TypeDef[];
  generics: null | [name: string, type: string];
  rest_props?: RestProps;
  extends?: { interface: string; import: string };
  componentComment?: string;
  componentCommentSource?: SourceRange;
  contexts?: ComponentContext[];
}

interface ComponentProp {
  name: string;
  localName?: string;
  kind: "let" | "const" | "function";
  constant: boolean;
  type?: string;
  typeSource?: "typescript" | "jsdoc" | "default" | "inferred" | "unknown";
  value?: string;
  defaultValue?: {
    raw: string;
    kind: "literal" | "array" | "object" | "expression" | "function" | "unknown";
    value?: unknown;
  };
  description?: string;
  params?: Array<{ name: string; type: string; description?: string; optional?: boolean }>;
  returnType?: string;
  isFunction: boolean;
  isFunctionDeclaration: boolean;
  isRequired: boolean;
  reactive: boolean;
  binding?: "readonly" | "writable";
  bindable?: true;
  source?: SourceRange;
}

interface ComponentSlot {
  name?: string | null;
  default: boolean;
  fallback?: string;
  slot_props?: string;
  description?: string;
  tags?: Array<{ name: string; body: string }>;
  source?: SourceRange;
}

type ComponentEvent =
  | {
      type: "forwarded";
      name: string;
      element: string;
      description?: string;
      detail?: string;
      source?: SourceRange;
    }
  | {
      type: "dispatched";
      name: string;
      detail?: string;
      description?: string;
      source?: SourceRange;
    };

source fields appear only when the Svelte or JavaScript AST has stable positions. They omit source text and raw character offsets.

SourcePosition.line is 1-based. SourcePosition.column is 0-based.

Prop metadata is additive and keeps the older public fields:

  • name is always the public prop name. For runes $props() aliases such as let { class: className } = $props(), localName is emitted only when the local binding differs.
  • typeSource identifies the conservative source of the emitted type: TypeScript annotation, JSDoc, initializer/default inference, other parser inference, or unknown fallback.
  • value remains the raw default expression string. defaultValue adds structured metadata with the same raw expression, a coarse kind, and a parsed value only for JSON-safe literals, arrays, and plain objects. sveld does not evaluate arbitrary code.
  • bindable: true is emitted only for props explicitly declared with Svelte 5 $bindable(...). Missing bindable should be treated as false.

API Reference

reactive

The reactive field in generated JSON is a heuristic. It does not fully answer whether a parent can use bind:prop in Svelte.

sveld marks reactive: true when it finds internal evidence that a prop is writable, including:

  • the prop is assigned or mutated inside the component
  • the prop is marked bindable in runes mode with $bindable(...)
  • the prop is used as the target of bind:* on an element or child component
  • wrapper-forwarded bindings such as bind:value, bind:selected, and bind:ref

Local variables or parameters that shadow a prop name do not count as writes to the exported prop.

reactive: false means sveld found no such evidence. It does not imply that parent-side bind: usage is impossible.

binding

The optional binding field documents a prop's intended bind: contract. It is separate from reactive and is never inferred from internal writes or $bindable().

Use @bindable readonly for component-owned or output-style bindings where the consumer binds to the current value emitted by the component:

<script>
  /**
   * Bind to the current value emitted by the component.
   * @bindable readonly
   */
  export let size = undefined;
</script>

Use @bindable writable for two-way or shared state bindings where either the consumer or component may control the value:

<script>
  /**
   * Bind to state controlled by either the consumer or component.
   * @bindable writable
   */
  export let open = false;
</script>

Generated JSON includes "binding": "readonly" or "binding": "writable" for annotated props. Unannotated props omit the field.

This is documentation only. Generated .svelte.d.ts prop types do not change. TypeScript cannot express Svelte binding direction reliably.

For stable output, generated events arrays are emitted in deterministic sorted order.

@type

Without a @type annotation, sveld will infer the primitive type for a prop:

export let kind = "primary";
// inferred type: "string"

For template literal default values, sveld infers the type as string:

export let id = `ccs-${Math.random().toString(36)}`;
// inferred type: "string"

Use the @type tag to document the type explicitly. In the example below, kind is a string union.

For lang="ts" components, prefer native TypeScript annotations when you already have them. @type still helps in JavaScript components, for overriding inferred types, and when the AST cannot recover a sharper type.

Signature:

/**
 * Optional description
 * @type {Type}
 */

Example:

Svelte 5 Runes:

<script>
  let {
    /**
     * Specify the kind of button
     * @type {"primary" | "secondary" | "tertiary"}
     */
    kind = "primary",
    /**
     * Specify the Carbon icon to render
     * @type {typeof import("carbon-icons-svelte").CarbonIcon}
     */
    renderIcon = Close20,
  } = $props();
</script>

For runes components with multiple destructured props, put JSDoc on the property you want to document. A declaration-level block is a fallback when the destructure exposes a single public prop.

Svelte 3, 4, 5 (non-Runes):

<script>
  /**
   * Specify the kind of button
   * @type {"primary" | "secondary" | "tertiary"}
   */
  export let kind = "primary";

  /**
   * Specify the Carbon icon to render
   * @type {typeof import("carbon-icons-svelte").CarbonIcon}
   */
  export let renderIcon = Close20;
</script>

Importing types

sveld supports TypeScript's import(...) type syntax, so a @type or @typedef can reference a type from another module without a top-level import. The expression is copied verbatim into the generated .d.ts and resolves the same way as hand-written TypeScript:

  • import("module").Type references an exported type.
  • typeof import("module").value references the type of an exported value.
  • import("svelte").ComponentProps<...> and other utility types compose with imports.

This keeps third-party types (Svelte stores, another component's props, library types) out of runtime imports while still showing up in IntelliSense for consumers.

Example:

<script>
  /**
   * A store from `svelte/store`. No top-level `import` required.
   * @type {import("svelte/store").Writable<string>}
   */
  export let value;

  /**
   * `typeof import(...)` references the type of a value export, here the
   * `writable` factory itself rather than a type it exports.
   * @type {typeof import("svelte/store").writable}
   */
  export let createStore;

  /**
   * Reuse another component's props with Svelte's `ComponentProps` utility.
   * @type {import("svelte").ComponentProps<import("svelte").SvelteComponent>}
   */
  export let buttonProps;

  /**
   * `import(...)` works inside a `@typedef` too, which helps when typing values shared via `setContext` / `getContext`.
   *
   * @typedef {{ rows: import("svelte/store").Writable<string[]>; selected: import("svelte/store").Readable<number> }} TableContext
   */

  /** @type {TableContext} */
  export let context;
</script>

Output:

export interface TableContext {
  rows: import("svelte/store").Writable<string[]>;
  selected: import("svelte/store").Readable<number>;
}

export type ComponentProps = {
  /** @default undefined */
  value: import("svelte/store").Writable<string>;
  /** @default undefined */
  createStore: typeof import("svelte/store").writable;
  /** @default undefined */
  buttonProps: import("svelte").ComponentProps<import("svelte").SvelteComponent>;
  /** @default undefined */
  context: TableContext;
};

Prefer unknown over any

When a prop accepts data whose shape you do not know ahead of time, annotate it as unknown rather than any. sveld preserves either keyword in the emitted prop type, but they behave differently for consumers: unknown forces a narrowing check before use; any disables type checking everywhere the value flows. Reserve any for real escape hatches.

Example:

<script>
  /**
   * A value of unknown shape. Prefer `unknown` over `any`: consumers must
   * narrow it before use instead of silently opting out of type checking.
   *
   * @type {unknown}
   */
  export let payload;

  /**
   * An escape hatch typed as `any`, shown for contrast. `any` disables type
   * checking everywhere it flows, so reach for `unknown` at boundaries instead.
   *
   * @type {any}
   */
  export let raw;
</script>

Output:

export type ComponentProps = {
  /** @default undefined */
  payload: unknown;
  /** @default undefined */
  raw: any;
};

Consumers must narrow an unknown prop before using it, while an any prop silently accepts anything:

function handle(props: ComponentProps) {
  // Error: 'payload' is of type 'unknown'. Narrow it first.
  props.payload.toUpperCase();

  if (typeof props.payload === "string") {
    props.payload.toUpperCase(); // OK after narrowing
  }
}

@default

By default, sveld infers the @default value from the prop's initializer and includes it in the generated TypeScript definitions:

<script>
  export let open = false;
</script>
/**
 * @default false
 */
open?: boolean;

Use @default to document the default value. When you supply @default, sveld uses it instead of the inferred value and avoids duplicate @default tags in the output.

Use @default when the initializer references a variable or expression that means nothing to consumers:

<script>
  const defaultFilter = () => true;

  /**
   * @default () => true
   * @type {(item: string, value: string) => boolean}
   */
  export let shouldFilter = defaultFilter;
</script>
/**
 * @default () => true
 */
shouldFilter?: (item: string, value: string) => boolean;

Identifier resolution

When a prop's initializer is a variable reference, sveld resolves it to the actual value:

<script>
  const DEFAULT_SIZE = "md";

  /** @type {"sm" | "md" | "lg"} */
  export let size = DEFAULT_SIZE;
</script>
/**
 * @default "md"
 */
size?: "sm" | "md" | "lg";

Chained references are also resolved:

<script>
  const ACTUAL_VALUE = 42;
  const ALIAS = ACTUAL_VALUE;

  export let count = ALIAS;
</script>
/**
 * @default 42
 */
count?: number;

Resolution follows up to 5 levels of indirection. Beyond that, the last resolved identifier name is used as the default value. If the identifier cannot be resolved (e.g., it is imported from another module), the variable name is used as-is.

When an explicit @default annotation is provided, it always takes precedence over the resolved value.

@typedef

The @typedef tag defines a shared type used multiple times in a component. All typedefs in a component are exported from the generated .d.ts.

Signature:

/**
 * @typedef {Type} TypeName
 */

Example:

Svelte 5 Runes:

<script>
  /**
   * @typedef {string} AuthorName
   * @typedef {{ name?: AuthorName; dob?: string; }} Author
   */

  let {
    /** @type {Author} */
    author = {},
    /** @type {Author[]} */
    authors = [],
  } = $props();
</script>

Svelte 3, 4, 5 (non-Runes):

<script>
  /**
   * @typedef {string} AuthorName
   * @typedef {{ name?: AuthorName; dob?: string; }} Author
   */

  /** @type {Author} */
  export let author = {};

  /** @type {Author[]} */
  export let authors = [];
</script>

Using @property for complex typedefs

For complex object types, use @property to document individual fields. That gives per-property tooltips in the IDE.

Signature:

/**
 * Type description
 * @typedef {object} TypeName
 * @property {Type} propertyName - Property description
 */

Example:

Svelte 5 Runes:

<script>
  /**
   * Represents a user in the system
   * @typedef {object} User
   * @property {string} name - The user's full name
   * @property {string} email - The user's email address
   * @property {number} age - The user's age in years
   */

  /** @type {User} */
  let { user = { name: "John", email: "[email protected]", age: 30 } } = $props();
</script>

Svelte 3, 4, 5 (non-Runes):

<script>
  /**
   * Represents a user in the system
   * @typedef {object} User
   * @property {string} name - The user's full name
   * @property {string} email - The user's email address
   * @property {number} age - The user's age in years
   */

  /** @type {User} */
  export let user = { name: "John", email: "[email protected]", age: 30 };
</script>

Output:

export type User = {
  /** The user's full name */
  name: string;
  /** The user's email address */
  email: string;
  /** The user's age in years */
  age: number;
};

export type ComponentProps = {
  /**
   * Represents a user in the system
   * @default { name: "John", email: "[email protected]", age: 30 }
   */
  user?: User;
};

Optional properties and default values

Use square brackets for optional properties, per JSDoc. Default values use [propertyName=defaultValue].

Signature:

/**
 * @typedef {object} TypeName
 * @property {Type} [optionalProperty] - Optional property description
 * @property {Type} [propertyWithDefault=defaultValue] - Property with default value
 */

Example:

Svelte 5 Runes:

<script>
  /**
   * Configuration options for the component
   * @typedef {object} ComponentConfig
   * @property {boolean} enabled - Whether the component is enabled
   * @property {string} theme - The component theme
   * @property {number} [timeout=5000] - Optional timeout in milliseconds
   * @property {boolean} [debug] - Optional debug mode flag
   */

  /** @type {ComponentConfig} */
  let { config = { enabled: true, theme: "dark" } } = $props();
</script>

Svelte 3, 4, 5 (non-Runes):

<script>
  /**
   * Configuration options for the component
   * @typedef {object} ComponentConfig
   * @property {boolean} enabled - Whether the component is enabled
   * @property {string} theme - The component theme
   * @property {number} [timeout=5000] - Optional timeout in milliseconds
   * @property {boolean} [debug] - Optional debug mode flag
   */

  /** @type {ComponentConfig} */
  export let config = { enabled: true, theme: "dark" };
</script>

Output:

export type ComponentConfig = {
  /** Whether the component is enabled */
  enabled: boolean;
  /** The component theme */
  theme: string;
  /** Optional timeout in milliseconds @default 5000 */
  timeout?: number;
  /** Optional debug mode flag */
  debug?: boolean;
};

export type ComponentProps = {
  /**
   * Configuration options for the component
   * @default { enabled: true, theme: "dark" }
   */
  config?: ComponentConfig;
};

The inline syntax @typedef {{ name: string }} User still works for backwards compatibility.

Discriminated unions

A @typedef can be a union of object literals, optionally mixed with primitive members. sveld emits these as export type X = ... aliases (not interface), so the discriminant narrows correctly on the consumer side.

Signature:

/**
 * @typedef {A | B | C} TypeName
 */

Example:

<script>
  /**
   * @typedef {{ kind: "success"; value: string } | { kind: "error"; error: Error }} Result
   * @typedef {{ ok: true; data: number } | { ok: false; reason: string } | "pending"} Status
   */

  /** @type {Result} */
  export let result = { kind: "success", value: "ok" };

  /** @type {Status} */
  export let status = "pending";
</script>

Output:

export type Result = { kind: "success"; value: string } | { kind: "error"; error: Error };

export type Status = { ok: true; data: number } | { ok: false; reason: string } | "pending";

export type ComponentProps = {
  /** @default { kind: "success", value: "ok" } */
  result?: Result;
  /** @default "pending" */
  status?: Status;
};

Consumers can then narrow on the discriminant:

function describe(r: Result) {
  switch (r.kind) {
    case "success":
      return r.value;
    case "error":
      return r.error.message;
  }
}

The same pattern works inline via @type, which is useful when the union is only used for a single prop:

/** @type {{ kind: "success"; value: string } | { kind: "error"; error: Error }} */
export let result = { kind: "success", value: "ok" };

In <script lang="ts"> components, write the type alias directly. sveld preserves it in the emitted .d.ts:

<script lang="ts">
  type Result = { kind: "success"; value: string } | { kind: "error"; error: Error };

  let { result = { kind: "success", value: "ok" } }: { result?: Result } = $props();
</script>

Branded types

A branded type is a primitive plus a unique marker so values like UserId are not interchangeable with any other string. At runtime it is still the underlying primitive; TypeScript treats the brand as a separate type. Declare the brand inline with @type. sveld copies the intersection verbatim into the emitted prop type, so the brand shows up in IntelliSense, autocomplete, and hover tooltips on the consumer side.

Example:

<script>
  /**
   * A branded string. At runtime it is a plain `string`, but the brand makes it
   * a distinct domain type that other strings cannot be assigned to.
   *
   * @type {string & { readonly __brand: "UserId" }}
   */
  export let userId;

  /**
   * A branded number representing a monetary amount in cents.
   *
   * @type {number & { readonly __brand: "Cents" }}
   */
  export let amount;
</script>

Output:

export type ComponentProps = {
  /**
   * A branded string. At runtime it is a plain `string`, but the brand makes it
   * a distinct domain type that other strings cannot be assigned to.
   * @default undefined
   */
  userId: string & { readonly __brand: "UserId" };

  /**
   * A branded number representing a monetary amount in cents.
   * @default undefined
   */
  amount: number & { readonly __brand: "Cents" };
};

Consumers construct branded values with a narrowing cast, then get compile-time protection against mixing them up:

const userId = "user_123" as ComponentProps["userId"];

// Error: a plain string is not assignable to the branded userId
component.$set({ userId: "user_123" });

Utility types

sveld preserves TypeScript utility types verbatim, so a prop type can be derived from an existing @typedef instead of restating its fields. Pick, Omit, Partial, Required, Readonly, ReturnType, Parameters, and Awaited pass through unchanged. When the base type changes, derived props follow.

Example:

<script>
  /**
   * @typedef {{ id: string; size: "sm" | "md" | "lg"; disabled: boolean }} Options
   */

  /**
   * @typedef {() => Options} Factory
   */

  /**
   * A subset of `Options`.
   * @type {Pick<Options, "id" | "size">}
   */
  export let summary;

  /**
   * Everything in `Options` except `disabled`.
   * @type {Omit<Options, "disabled">}
   */
  export let editable;

  /**
   * Derived from the factory's return type rather than restated.
   * @type {ReturnType<Factory>}
   */
  export let defaults;

  /**
   * The resolved value of an async source.
   * @type {Awaited<Promise<Options>>}
   */
  export let resolved;
</script>

Output:

export interface Options {
  id: string;
  size: "sm" | "md" | "lg";
  disabled: boolean;
}

export type Factory = () => Options;

export type ComponentProps = {
  summary: Pick<Options, "id" | "size">;
  editable: Omit<Options, "disabled">;
  defaults: ReturnType<Factory>;
  resolved: Awaited<Promise<Options>>;
};

Type guards

A prop typed as a type predicate (value is T) lets a component accept a user-defined type guard. sveld copies the predicate verbatim, whether you write it inline with @type or name it with @typedef, so narrowing survives in the generated .d.ts. Name guards isX or hasX, and make sure the implementation actually checks what the predicate claims.

Example:

<script>
  /**
   * @typedef {{ id: string; name: string }} User
   */

  /**
   * A type guard. It accepts an `unknown` value and returns a type predicate,
   * so callers can narrow `unknown` to `User` before accessing its fields.
   *
   * @type {(value: unknown) => value is User}
   */
  export let isUser;

  /**
   * A type guard expressed as a reusable `@typedef`.
   *
   * @typedef {(value: unknown) => value is User} UserGuard
   */

  /** @type {UserGuard} */
  export let validate;
</script>

Output:

export interface User {
  id: string;
  name: string;
}

export type UserGuard = (value: unknown) => value is User;

export type ComponentProps = {
  isUser: (value: unknown) => value is User;
  validate: UserGuard;
};

Consumers use the guard to narrow an unknown value:

function render(value: unknown, props: ComponentProps) {
  if (props.isUser(value)) {
    value.name; // narrowed to User
  }
}

@callback

The @callback tag defines a function type with @param and @returns, following the TypeScript JSDoc @callback spec. Like @typedef, callbacks are exported from the generated .d.ts.

Use it for callback props when you do not want inline function type syntax.

Signature:

/**
 * Optional description
 * @callback CallbackName
 * @param {Type} paramName - Parameter description
 * @returns {ReturnType}
 */

Example:

Svelte 5 Runes:

<script>
  /**
   * Callback fired when the value changes
   * @callback OnChange
   * @param {string} value - The new value
   * @param {number} index - The index of the changed item
   * @returns {void}
   */

  /** @type {OnChange} */
  let { onChange = (value, index) => {} } = $props();
</script>

Svelte 3, 4, 5 (non-Runes):

<script>
  /**
   * Callback fired when the value changes
   * @callback OnChange
   * @param {string} value - The new value
   * @param {number} index - The index of the changed item
   * @returns {void}
   */

  /** @type {OnChange} */
  export let onChange = (value, index) => {};
</script>

Output:

/**
 * Callback fired when the value changes
 */
export type OnChange = (value: string, index: number) => void;

export type ComponentProps = {
  /**
   * Callback fired when the value changes
   */
  onChange?: OnChange;
};

Callbacks can be combined with @typedef in the same comment block:

/**
 * @typedef {"asc" | "desc"} SortDirection
 * @callback SortFn
 * @param {any} a
 * @param {any} b
 * @param {SortDirection} direction
 * @returns {number}
 */

When @returns is omitted, the return type defaults to void. When no @param tags are present, the callback is typed as a no-argument function.

@slot / @snippet

Use @slot to type component slots. In Svelte 5 runes components, @snippet is an alias. Both are non-standard JSDoc tags.

Descriptions are optional for every slot, including the default slot. Put prose in the same /** */ block above @slot / @snippet, or an inline description on the @slot line for named slots.

Signature:

/**
 * @slot {Type} slot-name [slot description]
 * @snippet {Type} snippet-name [snippet description]
 */

Omit the `slot-name` to type the default slot.

/**
 * @slot {Type}
 * @snippet {Type}
 */

Example:

Svelte 5 Runes:

<script>
  /**
   * @snippet {{ prop: number; doubled: number; }}
   * @snippet {{}} title
   * @snippet {{ prop: number }} body - Customize the paragraph text.
   */

  let { prop = 0, children, title, body } = $props();
</script>

<h1>
  {@render children?.({ prop, doubled: prop * 2 })}
  {@render title?.()}
</h1>

<p>
  {@render body?.({ prop })}
</p>

Svelte 3, 4, 5 (non-Runes):

<script>
  /**
   * @slot {{ prop: number; doubled: number; }}
   * @slot {{}} title
   * @slot {{ prop: number }} body - Customize the paragraph text.
   */

  export let prop = 0;
</script>

<h1>
  <slot {prop} doubled={prop * 2} />
  <slot name="title" />
</h1>

<p>
  <slot name="body" {prop} />
</p>

Extra JSDoc tags before @slot

Tags such as @example, @deprecated, @see, or @since that appear after the prose description and before the @slot / @snippet line are copied into generated .d.ts files. The emitted JSDoc above each slot's snippet prop (and the traditional SlotDefs shape) lists the description, then those tags in source order. The same entries appear in JSON as tags: [{ "name", "body" }, ...].

Put @slot / @snippet last in the block (description, optional extra tags, slot tag). Tags after @slot / @snippet in the same comment are not tied to that slot. Unknown tag names pass through as-is. Markdown docs do not render slot descriptions or these tags yet; use TypeScript hover or JSON.

Example (default slot with @example and @deprecated):

<script>
  /**
   * Spread `props` onto a custom element.
   * @example
   * ```svelte
   * <Item let:props>
   *   <a {...props} href="/">Home</a>
   * </Item>
   * ```
   * @deprecated Prefer the `link` snippet.
   * @slot {{ props?: { class: string } }}
   */
</script>

<slot props={{ class: "bx--link" }} />

Svelte 5 Snippet Compatibility

For Svelte 5, sveld generates optional snippet props for all slots so consumers can use traditional slot syntax or {#snippet}.

When parsing runes components, sveld maps {@render ...} calls back into the same slot metadata used for <slot>. Reserved snippet props like children, plus named snippet props from {@render ...}, live in slots metadata and generated snippet prop types, not duplicated in props.

Positional snippet calls like {@render row?.(item, index)} stay typed props when the prop has an explicit type like Snippet<[Item, number]>. They are not turned into synthetic slot metadata.

For slots with props (e.g., let:prop), the generated type uses a Snippet-compatible signature:

slotName?: (this: void, ...args: [{ prop: PropType }]) => void;

For slots without props:

slotName?: (this: void) => void;

Why this signature?

  • this: void blocks calling the snippet with a this context, matching Svelte's rule that snippets are pure render functions
  • ...args: [Props] uses tuple spread for type-safe parameters. It accepts fixed-length tuples (like [{ row: Row }]) and rejects array types (like Props[]), matching Svelte's Snippet<T> type

Default slot (children prop):

The default slot generates an optional children snippet prop:

<!-- Component with default slot that passes props -->
<Dropdown {items} selectedId="1">
  {#snippet children({ item, index })}
    <span>{item.text} (#{index})</span>
  {/snippet}
</Dropdown>

Generated types:

type DropdownProps = {
  items: Item[];
  selectedId?: string;

  // Default slot as children snippet prop
  children?: (this: void, ...args: [{ item: Item; index: number }]) => void;
};

Named slots:

<!-- Using the generated types with Svelte 5 syntax -->
<DataTable headers={headers} rows={rows}>
  {#snippet cell({ cell, row })}
    {#if cell.key === 'actions'}
      <Button on:click={() => handleAction(row)}>Edit</Button>
    {:else}
      {cell.value}
    {/if}
  {/snippet}
</DataTable>

Generated output includes both the snippet prop and the traditional slot definition:

type DataTableProps<Row> = {
  // ... other props

  // Snippet prop for Svelte 5 compatibility
  cell?: (
    this: void,
    ...args: [
      {
        row: Row;
        cell: DataTableCell<Row>;
        rowIndex: number;
        cellIndex: number;
      },
    ]
  ) => void;

  // Default slot as children prop
  children?: (this: void) => void;
};

export default class DataTable<Row> extends SvelteComponentTyped<
  DataTableProps<Row>,
  {
    /* events */
  },
  {
    // Traditional slot definition (Svelte 3/4)
    default: Record<string, never>;
    cell: {
      row: Row;
      cell: DataTableCell<Row>;
      rowIndex: number;
      cellIndex: number;
    };
  }
> {}

@event

Use the @event tag to type dispatched events. An event name is required and a description optional.

In Svelte 5 runes components, callback props like onclick are props, not events. The events output stays reserved for dispatched events and legacy forwarded events. If a runes component documents @event foo and exposes a matching callback prop like onfoo without actually dispatching or forwarding foo, sveld aliases that documentation onto the callback prop instead of synthesizing an emitted event.

Use null as the value if no event detail is provided.

Signature:

/**
 * Optional event description
 * @event {EventDetail} eventname [inline description]
 */

Example:

Svelte 5 Runes:

<script>
  /**
   * Fired when a value is saved.
   * @event {{ id: string }} save
   */
  let { onsave } = $props();
</script>

<button onclick={() => onsave?.({ id: "1" })}>Save</button>

Svelte 5 Runes with dispatched events:

<script>
  /**
   * @event {{ key: string }} button:key
   * @event {null} key - Fired when `key` changes.
   */

  let { key = "" } = $props();

  import { createEventDispatcher } from "svelte";

  const dispatch = createEventDispatcher();

  $effect(() => {
    dispatch("button:key", { key });
    if (key) dispatch("key");
  });
</script>

Svelte 3, 4, 5 (non-Runes):

<script>
  /**
   * @event {{ key: string }} button:key
   * @event {null} key - Fired when `key` changes.
   */

  export let key = "";

  import { createEventDispatcher } from "svelte";

  const dispatch = createEventDispatcher();

  $: dispatch("button:key", { key });
  $: if (key) dispatch("key");
</script>

Output:

export default class Component extends SvelteComponentTyped<
  ComponentProps,
  {
    "button:key": CustomEvent<{ key: string }>;
    /** Fired when `key` changes. */ key: CustomEvent<null>;
  },
  Record<string, never>
> {}

Using @property for complex event details

For events with complex object payloads, use @property to document individual fields. The main comment becomes the event description.

Signature:

/**
 * Event description
 * @event eventname
 * @type {object}
 * @property {Type} propertyName - Property description
 */

Example:

Svelte 5 Runes:

<script>
  /**
   * Fired when the user submits the form
   *
   * @event submit
   * @type {object}
   * @property {string} name - The user's name
   * @property {string} email - The user's email address
   * @property {boolean} newsletter - Whether the user opted into the newsletter
   */

  let { name = "Jane Doe", email = "[email protected]", newsletter = true } = $props();

  import { createEventDispatcher } from "svelte";

  const dispatch = createEventDispatcher();

  function handleSubmit() {
    dispatch("submit", { name, email, newsletter });
  }
</script>

<button type="button" onclick={handleSubmit}>Submit</button>

Svelte 3, 4, 5 (non-Runes):

<script>
  /**
   * Fired when the user submits the form
   *
   * @event submit
   * @type {object}
   * @property {string} name - The user's name
   * @property {string} email - The user's email address
   * @property {boolean} newsletter - Whether the user opted into the newsletter
   */

  export let name = "Jane Doe";
  export let email = "[email protected]";
  export let newsletter = true;

  import { createEventDispatcher } from "svelte";

  const dispatch = createEventDispatcher();

  function handleSubmit() {
    dispatch("submit", { name, email, newsletter });
  }
</script>

<button type="button" on:click={handleSubmit}>Submit</button>

Output:

export default class Component extends SvelteComponentTyped<
  ComponentProps,
  {
    /** Fired when the user submits the form */
    submit: CustomEvent<{
      /** The user's name */
      name: string;
      /** The user's email address */
      email: string;
      /** Whether the user opted into the newsletter */
      newsletter: boolean;
    }>;
  },
  Record<string, never>
> {}

Optional properties in event details

Like typedefs, you can mark event detail properties as optional with square brackets when they are not always in the payload.

Example:

Svelte 5 Runes:

<script>
  /**
   * Snowball event fired when throwing a snowball
   *
   * @event snowball
   * @type {object}
   * @property {boolean} isPacked - Indicates whether the snowball is tightly packed
   * @property {number} speed - The speed of the snowball in mph
   * @property {string} [color] - Optional color of the snowball
   * @property {number} [density=0.9] - Optional density with default value
   */

  let { speed = 50 } = $props();

  import { createEventDispatcher } from "svelte";

  const dispatch = createEventDispatcher();

  function throwSnowball() {
    dispatch("snowball", {
      isPacked: true,
      speed,
    });
  }
</script>

<button type="button" onclick={throwSnowball}>Throw</button>

Svelte 3, 4, 5 (non-Runes):

<script>
  /**
   * Snowball event fired when throwing a snowball
   *
   * @event snowball
   * @type {object}
   * @property {boolean} isPacked - Indicates whether the snowball is tightly packed
   * @property {number} speed - The speed of the snowball in mph
   * @property {string} [color] - Optional color of the snowball
   * @property {number} [density=0.9] - Optional density with default value
   */

  export let speed = 50;

  import { createEventDispatcher } from "svelte";

  const dispatch = createEventDispatcher();

  function throwSnowball() {
    dispatch("snowball", {
      isPacked: true,
      speed,
    });
  }
</script>

<button type="button" on:click={throwSnowball}>Throw</button>

Output:

export default class Component extends SvelteComponentTyped<
  ComponentProps,
  {
    /** Snowball event fired when throwing a snowball */
    snowball: CustomEvent<{
      /** Indicates whether the snowball is tightly packed */
      isPacked: boolean;
      /** The speed of the snowball in mph */
      speed: number;
      /** Optional color of the snowball */
      color?: string;
      /** Optional density with default value @default 0.9 */
      density?: number;
    }>;
  },
  Record<string, never>
> {}

Discriminated unions in event details

When the event detail is a union (or any non-object shape), use @type to declare it directly. An explicit @type wins over @property tags, so the union is copied verbatim into the emitted .d.ts instead of being flattened into independent property unions. The only exception is @type {object}, which tells sveld to build the shape from @property tags (as shown above).

Example:

<script>
  /**
   * @event sort
   * @type {{ key: null; direction: "none" } | { key: string; direction: "ascending" | "descending" }}
   * Dispatched when a sortable column header would change the active sort.
   */

  import { createEventDispatcher } from "svelte";

  const dispatch = createEventDispatcher();
</script>

Output:

export default class Component extends SvelteComponentTyped<
  ComponentProps,
  {
    /** Dispatched when a sortable column header would change the active sort. */
    sort: CustomEvent<{ key: null; direction: "none" } | { key: string; direction: "ascending" | "descending" }>;
  },
  Record<string, never>
> {}

Any free-text prose after the tags is attached to the event description, not to a property doc.

Context API

sveld generates TypeScript definitions for Svelte's setContext/getContext by extracting types from JSDoc on context values.

How it works

When you call setContext in a component, sveld:

  1. Detects the setContext call
  2. Extracts the context key (must be a string literal)
  3. Finds JSDoc @type annotations on the variables being passed
  4. Generates a TypeScript type export for the context

Example

Modal.svelte

Svelte 5 Runes:

<script>
  import { setContext } from "svelte";

  /**
   * Close the modal
   * @type {() => void}
   */
  const close = () => {
    // Close logic
  };

  /**
   * Open the modal with content
   * @type {(component: any, props?: any) => void}
   */
  const open = (component, props) => {
    // Open logic
  };

  setContext("simple-modal", { open, close });

  let { children } = $props();
</script>

<div class="modal">
  {@render children?.()}
</div>

Svelte 3, 4, 5 (non-Runes):

<script>
  import { setContext } from "svelte";

  /**
   * Close the modal
   * @type {() => void}
   */
  const close = () => {
    // Close logic
  };

  /**
   * Open the modal with content
   * @type {(component: any, props?: any) => void}
   */
  const open = (component, props) => {
    // Open logic
  };

  setContext("simple-modal", { open, close });
</script>

<div class="modal">
  <slot />
</div>

Generated TypeScript definition:

export type SimpleModalContext = {
  /** Open the modal with content */
  open: (component: any, props?: any) => void;
  /** Close the modal */
  close: () => void;
};

export type ModalProps = {};

export default class Modal extends SvelteComponentTyped<
  ModalProps,
  Record<string, any>,
  { default: Record<string, never> }
> {}

Consumer usage:

<script>
  import { getContext } from 'svelte';
  import type { SimpleModalContext } from 'modal-library/Modal.svelte';

  const { close, open } = getContext<SimpleModalContext>('simple-modal');
</script>

<button on:click={close}>Close</button>

Explicitly typing contexts

There are several ways to type contexts:

Option 1: Inline JSDoc on variables (recommended)

<script>
  import { setContext } from 'svelte';

  /**
   * @type {() => void}
   */
  const close = () => {};

  setContext('modal', { close });
</script>

Option 2: Using @typedef for complex types

<script>
  import { setContext } from 'svelte';

  /**
   * @typedef {object} TabData
   * @property {string} id
   * @property {string} label
   * @property {boolean} [disabled]
   */

  /**
   * @type {(tab: TabData) => void}
   */
  const addTab = (tab) => {};

  setContext('tabs', { addTab });
</script>

Option 3: Referencing imported types

<script>
  import { setContext } from 'svelte';

  /**
   * @type {typeof import("./types").ModalAPI}
   */
  const modalAPI = {
    open: () => {},
    close: () => {}
  };

  setContext('modal', modalAPI);
</script>

Option 4: Direct object literal with inline functions

<script>
  import { setContext } from 'svelte';

  // sveld infers basic function signatures
  setContext('modal', {
    open: (component, props) => {}, // Inferred as (arg, arg) => any
    close: () => {}                 // Inferred as () => any
  });
</script>

Inline functions without @type annotations get generic inferred signatures. Add explicit JSDoc when you care about the shape.

Notes

  • Context keys must be string literals (dynamic keys are not supported)
  • Variables passed to setContext should have JSDoc @type annotations for accurate types
  • The generated type name follows the pattern: {PascalCase}Context. Separators (hyphens, underscores, dots, colons, slashes, spaces) are stripped and each segment is capitalized: | Context Key | Generated Type Name | | --- | --- | | "simple-modal" | SimpleModalContext | | "user_settings" | UserSettingsContext | | "Carbon.Modal" | CarbonModalContext | | "Carbon:Modal" | CarbonModalContext | | "app/modal" | AppModalContext | | "My Context" | MyContextContext | | "Tabs" | TabsContext |
  • If no type annotation is found, the type defaults to any with a warning

@restProps

sveld can detect inline HTML elements that $$restProps is forwarded to. It cannot infer the underlying element for instantiated components.

Use @restProps to name the element tags $$restProps is forwarded to.

Signature:

/**
 * Single element
 * @restProps {tagname}
 *
 * Multiple elements
 * @restProps {tagname-1 | tagname-2 | tagname-3}
 */

Example:

Svelte 5 Runes:

<script>
  import Button from "./Button.svelte";

  /** @restProps {h1 | button} */
  let { edit = false, children, ...restProps } = $props();
</script>

{#if edit}
  <Button {...restProps} />
{:else}
  <h1 {...restProps}>
    {@render children?.()}
  </h1>
{/if}

Svelte 3, 4, 5 (non-Runes):

<script>
  /** @restProps {h1 | button} */
  export let edit = false;

  import Button from "./Button.svelte";
</script>

{#if edit}
  <Button {...$$restProps} />
{:else}
  <h1 {...$$restProps}><slot /></h1>
{/if}

@extendProps

When a component wraps another, use @extendProps to extend generated props.

@extends works as an alias, but prefer @extendProps to avoid clashing with standard JSDoc @extends for class inheritance.

Signature:

/**
 * @extendProps {<relative path to component>} ComponentProps
 */

Example:

/** @extendProps {"./Button.svelte"} ButtonProps */

export const secondary = true;

import Button from "./Button.svelte";

@template

Svelte supports defining generics via the generics attribute on the script tag, but this requires lang="ts".

<!-- Requires lang="ts" -->
<script lang="ts" generics="Row extends DataTableRow = any"></script>

Because sveld targets JavaScript-only usage as a baseline, generics use the standard JSDoc @template tag. @generics is also supported as an alias.

Signature: Uses standard JSDoc @template syntax:

/**
 * @template {Constraint} [Name=Default]
 */

Example:

/**
 * @template {DataTableRow} [Row=DataTableRow]
 */

Component example:

Svelte 5 Runes:

<script>
  /**
   * @typedef {{ id: string | number; [key: string]: any; }} DataTableRow
   * @typedef {Exclude<keyof Row, "id">} DataTableKey<Row>
   * @typedef {{ key: DataTableKey<Row>; value: string; }} DataTableHeader<Row=DataTableRow>
   * @template {DataTableRow} <Row extends DataTableRow = DataTableRow>
   * @generics {Row extends DataTableRow = DataTableRow} Row
   */

  let {
    /** @type {ReadonlyArray<DataTableHeader<Row>>} */
    headers = [],
    /** @type {ReadonlyArray<Row>} */
    rows = [],
    children,
  } = $props();
</script>

{@render children?.({ headers, rows })}

Svelte 3, 4, 5 (non-Runes):

<script>
  /**
   * @typedef {{ id: string | number; [key: string]: any; }} DataTableRow
   * @typedef {Exclude<keyof Row, "id">} DataTableKey<Row>
   * @typedef {{ key: DataTableKey<Row>; value: string; }} DataTableHeader<Row=DataTableRow>
   * @template {DataTableRow} <Row extends DataTableRow = DataTableRow>
   * @generics {Row extends DataTableRow = DataTableRow} Row
   */

  /** @type {ReadonlyArray<DataTableHeader<Row>>} */
  export let headers = [];

  /** @type {ReadonlyArray<Row>} */
  export let rows = [];
</script>

<slot {headers} {rows} />

Generated output looks like this:

export type ComponentProps<Row extends DataTableRow = DataTableRow> = {
  rows?: ReadonlyArray<Row>;
};

export default class Component<
  Row extends DataTableRow = DataTableRow,
> extends SvelteComponentTyped<
  ComponentProps<Row>,
  Record<string, any>,
  Record<string, any>
> {}

For multiple generics, use separate @template tags:

/**
 * @template {DataTableRow} [Row=DataTableRow]
 * @template {DataTableRow} [Header=DataTableRow]
 */
export type ComponentProps<
  Row extends DataTableRow = DataTableRow,
  Header extends DataTableRow = DataTableRow,
> = { ... };

export default class Component<
  Row extends DataTableRow = DataTableRow,
  Header extends DataTableRow = DataTableRow,
> extends SvelteComponentTyped<
  ComponentProps<Row, Header>,
  Record<string, any>,
  Record<string, any>
> {}

@generics

As an alternative to @template, sveld supports @generics. Unlike @template, which JSDoc/TypeScript support officially, @generics is sveld-specific. The syntax can be easier to read because the full constraint is inline:

/**
 * @generics {Row extends DataTableRow = DataTableRow} Row
 */

This is equivalent to:

/**
 * @template {DataTableRow} [Row=DataTableRow]
 */

For multiple generics, use a single @generics tag with comma-separated names:

/**
 * @generics {Row extends DataTableRow = DataTableRow, Header extends DataTableRow = DataTableRow} Row,Header
 */

@component comments

The Svelte Language Server supports component-level comments through the following syntax: <!-- @component [comment] -->.

sveld will copy these over to the exported default component in the TypeScript definition.

Example:

Svelte 5 Runes:

<!-- @component
@example
<Button>
  Text
</Button>
-->
<script>
  let { children } = $props();
</script>

<button>
  {@render children?.()}
</button>

Svelte 3, 4, 5 (non-Runes):

<!-- @component
@example
<Button>
  Text
</Button>
-->
<button>
  <slot />
</button>

Output:

/**
 * @example
 * <Button>
 *   Text
 * </Button>
 */
export default class Button extends SvelteComponentTyped<
  ButtonProps,
  Record<string, any>,
  { default: Record<string, never> }
> {}

Accessor Props

Exported functions and consts become accessor props in generated TypeScript definitions. Use @type for function signatures, or @param and @returns (or @return) for richer docs.

@type wins over @param/@returns when both are present.

Signature:

/**
 * Function description
 * @param {Type} paramName - Parameter description
 * @param {Type} [optionalParam] - Optional parameter
 * @returns {ReturnType} Return value description
 */

Example:

Svelte 5 Runes:

<script>
  /**
   * @typedef {object} NotificationData
   * @property {string} [id] - Optional id for deduplication
   * @property {"error" | "info" | "success"} [kind]
   */

  let { children } = $props();

  /**
   * Add a notification to the queue.
   * @param {NotificationData} notification
   * @returns {string} The notification id
   */
  export function add(notification) {
    const id = notification.id ?? "id";
    return id;
  }

  /**
   * Remove a notification by id.
   * @param {string} id
   * @returns {boolean} True if the notification was found and removed
   */
  export function remove(id) {
    return true;
  }

  /**
   * Get notification count.
   * @returns {number} The number of notifications
   */
  export function getCount() {
    return 0;
  }
</script>

<div>
  {@render children?.()}
</div>

Svelte 3, 4, 5 (non-Runes):

<script>
  /**
   * @typedef {object} NotificationData
   * @property {string} [id] - Optional id for deduplication
   * @property {"error" | "info" | "success"} [kind]
   */

  /**
   * Add a notification to the queue.
   * @param {NotificationData} notification
   * @returns {string} The notification id
   */
  export function add(notification) {
    const id = notification.id ?? "id";
    return id;
  }

  /**
   * Remove a notification by id.
   * @param {string} id
   * @returns {boolean} True if the notification was found and removed
   */
  export function remove(id) {
    return true;
  }

  /**
   * Get notification count.
   * @returns {number} The number of notifications
   */
  export function getCount() {
    return 0;
  }
</script>

Output:

export type NotificationData = {
  /** Optional id for deduplication */
  id?: string;
  kind?: "error" | "info" | "success";
};

export type ComponentProps = Record<string, never>;

export default class Component extends SvelteComponentTyped<
  ComponentProps,
  Record<string, any>,
  Record<string, never>
> {
  /**
   * Add a notification to the queue.
   */
  add: (notification: NotificationData) => string;

  /**
   * Remove a notification by id.
   */
  remove: (id: string) => boolean;

  /**
   * Get notification count.
   */
  getCount: () => number;
}

When only @param tags are present without @returns, the return type defaults to any. When only @returns is present without @param, the function signature is () => returnType.

Contributing

See contributing guidelines.

License

Apache-2.0