https://github.com/carbon-design-system/sveld
Generate TypeScript definitions and component documentation for your Svelte components
https://github.com/carbon-design-system/sveld
docgen documentation jsdoc svelte svelte-component typescript-definitions
Last synced: 2 months ago
JSON representation
Generate TypeScript definitions and component documentation for your Svelte components
- Host: GitHub
- URL: https://github.com/carbon-design-system/sveld
- Owner: carbon-design-system
- License: apache-2.0
- Created: 2020-11-16T17:40:13.000Z (over 5 years ago)
- Default Branch: main
- Last Pushed: 2026-03-30T16:24:58.000Z (3 months ago)
- Last Synced: 2026-04-03T03:57:28.965Z (2 months ago)
- Topics: docgen, documentation, jsdoc, svelte, svelte-component, typescript-definitions
- Language: TypeScript
- Homepage: https://sveld.onrender.com
- Size: 2.42 MB
- Stars: 443
- Watchers: 4
- Forks: 22
- Open Issues: 1
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- Contributing: CONTRIBUTING.md
- Funding: .github/FUNDING.yml
- License: LICENSE
Awesome Lists containing this project
- awesome-list - sveld - design-system | 182 | (Svelte)
README
# sveld
[![NPM][npm]][npm-url]

`sveld` generates TypeScript definitions and component documentation (Markdown/JSON) for Svelte components. It analyzes props, events, slots, and other component features through static analysis. Types and signatures can be defined using [JSDoc notation](https://jsdoc.app/).
The purpose of this project is to make third party Svelte component libraries compatible with the Svelte Language Server and TypeScript with minimal effort required by the author. For example, TypeScript definitions may be used during development via intelligent code completion in Integrated Development Environments (IDEs) like VSCode.
[Carbon Components Svelte](https://github.com/carbon-design-system/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`, ``, `$$restProps`, …), and Svelte 5 Runes (`$props()`, `$bindable()`, `{@render ...}`, callback props such as `onclick`, …).
For `lang="ts"` components, `sveld` preserves source-level prop type annotations when possible instead of requiring JSDoc as the primary source of truth. This includes 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 | ✓ |
**Note** that 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.
---
Given a Svelte component, `sveld` can infer basic prop types to generate TypeScript definitions compatible with the [Svelte Language Server](https://github.com/sveltejs/language-tools):
**Button.svelte**
```svelte
export let type = "button";
export let primary = false;
Click me
```
The following generated `.d.ts` extends `SvelteComponentTyped`:
**Button.svelte.d.ts**
```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 }
> {}
```
Sometimes, inferring prop types is insufficient.
Prop/event/slot types and signatures can be augmented using [JSDoc](https://jsdoc.app/) notations.
```js
/** @type {"button" | "submit" | "reset"} */
export let type = "button";
/**
* Set to `true` to use the primary variant
*/
export let primary = false;
```
The accompanying JSDoc annotations would generate the following:
```ts
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 }
> {}
```
---
## Table of Contents
- [Approach](#approach)
- [Usage](#usage)
- [Installation](#installation)
- [Vite](#vite)
- [Node.js](#nodejs)
- [CLI](#cli)
- [Publishing to NPM](#publishing-to-npm)
- [Available Options](#available-options)
- [API Reference](#api-reference)
- [@type](#type)
- [@default](#default)
- [@typedef](#typedef)
- [@callback](#callback)
- [@slot / @snippet](#slot--snippet)
- [Svelte 5 Snippet Compatibility](#svelte-5-snippet-compatibility)
- [@event](#event)
- [Context API](#context-api)
- [@restProps](#restprops)
- [@extendProps](#extendprops)
- [@generics](#generics)
- [@component comments](#component-comments)
- [Accessor Props](#accessor-props)
- [Contributing](#contributing)
- [License](#license)
## Approach
`sveld` uses the Svelte compiler to statically analyze Svelte components exported from a library to generate documentation useful to the end user.
Extracted metadata include:
- props
- slots
- forwarded events
- dispatched events
- context (setContext/getContext)
- `$$restProps`
This library adopts a progressively enhanced approach. Any property type that cannot be inferred (e.g., "hello" is a string) falls back to "any" to minimize incorrectly typed properties or signatures. To mitigate this, the library author can add JSDoc annotations to specify types that cannot be reliably inferred. This represents a progressively enhanced approach because JSDocs are comments that can be ignored by the compiler.
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` intentionally stays AST-only. It preserves imported and local type text in generated `.d.ts` output, but it does not perform project-wide semantic resolution with the TypeScript compiler. That means opaque imported whole-object `$props()` types can be preserved in declarations without being fully expanded into JSON metadata.
## Usage
### Installation
Install `sveld` as a development dependency.
```sh
# 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).
```ts
// 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, `sveld` also works in Rollup configurations.
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:
```js
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:
```diff
sveld({
+ typesOptions: {
+ outDir: 'dist',
+ printWidth: 80
+ }
})
```
### CLI
The CLI uses the `"svelte"` field from your `package.json` as the entry point:
```sh
npx sveld
```
Generate documentation in JSON and/or Markdown formats using the following flags:
```sh
npx sveld --json --markdown
```
### Node.js
You can also use `sveld` programmatically in Node.js. The package is **ESM-only**; `require("sveld")` is not supported. Use `import` or dynamic `import()`.
If no `input` is specified, `sveld` will infer the entry point based on the `package.json#svelte` field.
```js
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`
If `json` is `true`, a `COMPONENT_API.json` file will be generated at the root of your project. This file contains documentation for all components.
Use the `jsonOptions.outDir` option to specify the folder for individual JSON files to be emitted.
```js
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 are outputted to the `types` folder by default. Don't forget to include the folder in your `package.json` when publishing the package to NPM.
```diff
{
"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`.
```diff
sveld({
+ markdown: true,
+ json: true,
})
```
## API Reference
### `reactive`
The `reactive` field in generated JSON is heuristic metadata. It is not a complete statement of whether a parent may 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.
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:
```js
export let kind = "primary";
// inferred type: "string"
```
For template literal default values, `sveld` infers the type as `string`:
```js
export let id = `ccs-${Math.random().toString(36)}`;
// inferred type: "string"
```
Use the `@type` tag to explicitly document the type. In the following example, the `kind` property has an enumerated (enum) type.
For `lang="ts"` components, prefer native TypeScript annotations when they are already present. `@type` remains useful for JavaScript components, for overriding inferred types, and for cases where the AST cannot recover a more precise type.
**Signature:**
```js
/**
* Optional description
* @type {Type}
*/
```
**Example:**
**Svelte 5 Runes:**
```svelte
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();
```
For runes components with multiple destructured props, place JSDoc on the individual property you want to document. A declaration-level JSDoc block is only used as a fallback when the destructure exposes a single public prop.
**Svelte 3, 4, 5 (non-Runes):**
```svelte
/**
* 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;
```
### `@default`
By default, `sveld` infers the `@default` value from the prop's initializer and includes it in the generated TypeScript definitions:
```svelte
export let open = false;
```
```ts
/**
* @default false
*/
open?: boolean;
```
Use the `@default` tag to explicitly document the default value. When an explicit `@default` annotation is provided, `sveld` uses it instead of the inferred value, avoiding duplicate `@default` tags in the output.
This is useful when the initializer references a variable or expression that is not meaningful to consumers:
```svelte
const defaultFilter = () => true;
/**
* @default () => true
* @type {(item: string, value: string) => boolean}
*/
export let shouldFilter = defaultFilter;
```
```ts
/**
* @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 automatically:
```svelte
const DEFAULT_SIZE = "md";
/** @type {"sm" | "md" | "lg"} */
export let size = DEFAULT_SIZE;
```
```ts
/**
* @default "md"
*/
size?: "sm" | "md" | "lg";
```
Chained references are also resolved:
```svelte
const ACTUAL_VALUE = 42;
const ALIAS = ACTUAL_VALUE;
export let count = ALIAS;
```
```ts
/**
* @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 can be used to define a common type that is used multiple times within a component. All typedefs defined in a component will be exported from the generated TypeScript definition file.
**Signature:**
```js
/**
* @typedef {Type} TypeName
*/
```
**Example:**
**Svelte 5 Runes:**
```svelte
/**
* @typedef {string} AuthorName
* @typedef {{ name?: AuthorName; dob?: string; }} Author
*/
let {
/** @type {Author} */
author = {},
/** @type {Author[]} */
authors = [],
} = $props();
```
**Svelte 3, 4, 5 (non-Runes):**
```svelte
/**
* @typedef {string} AuthorName
* @typedef {{ name?: AuthorName; dob?: string; }} Author
*/
/** @type {Author} */
export let author = {};
/** @type {Author[]} */
export let authors = [];
```
#### Using `@property` for complex typedefs
For complex object types, use the `@property` tag to document individual properties. This provides better documentation and IDE support with per-property tooltips.
**Signature:**
```js
/**
* Type description
* @typedef {object} TypeName
* @property {Type} propertyName - Property description
*/
```
**Example:**
**Svelte 5 Runes:**
```svelte
/**
* 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: "john@example.com", age: 30 } } = $props();
```
**Svelte 3, 4, 5 (non-Runes):**
```svelte
/**
* 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: "john@example.com", age: 30 };
```
Output:
```ts
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: "john@example.com", age: 30 }
*/
user?: User;
};
```
#### Optional properties and default values
Following JSDoc standards, use square brackets to mark properties as optional. You can also specify default values using the `[propertyName=defaultValue]` syntax.
**Signature:**
```js
/**
* @typedef {object} TypeName
* @property {Type} [optionalProperty] - Optional property description
* @property {Type} [propertyWithDefault=defaultValue] - Property with default value
*/
```
**Example:**
**Svelte 5 Runes:**
```svelte
/**
* 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();
```
**Svelte 3, 4, 5 (non-Runes):**
```svelte
/**
* 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" };
```
Output:
```ts
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;
};
```
> **Note:** The inline syntax `@typedef {{ name: string }} User` continues to work for backwards compatibility.
### `@callback`
The `@callback` tag defines a function type using `@param` and `@returns` tags, following the [TypeScript JSDoc `@callback` specification](https://www.typescriptlang.org/docs/handbook/jsdoc-supported-types.html#callback). Like `@typedef`, callbacks are exported from the generated TypeScript definition file.
This is useful for typing callback props without using inline function type syntax.
**Signature:**
```js
/**
* Optional description
* @callback CallbackName
* @param {Type} paramName - Parameter description
* @returns {ReturnType}
*/
```
**Example:**
**Svelte 5 Runes:**
```svelte
/**
* 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();
```
**Svelte 3, 4, 5 (non-Runes):**
```svelte
/**
* 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) => {};
```
Output:
```ts
/**
* 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:
```js
/**
* @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 the `@slot` tag for typing component slots. For Svelte 5 runes components, `@snippet` is also supported as an alias. Both are non-standard JSDoc tags.
Descriptions are optional for named slots. Currently, the default slot cannot have a description.
**Signature:**
```js
/**
* @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:**
```svelte
/**
* @snippet {{ prop: number; doubled: number; }}
* @snippet {{}} title
* @snippet {{ prop: number }} body - Customize the paragraph text.
*/
let { prop = 0, children, title, body } = $props();
{@render children?.({ prop, doubled: prop * 2 })}
{@render title?.()}
{@render body?.({ prop })}
```
**Svelte 3, 4, 5 (non-Runes):**
```svelte
/**
* @slot {{ prop: number; doubled: number; }}
* @slot {{}} title
* @slot {{ prop: number }} body - Customize the paragraph text.
*/
export let prop = 0;
```
#### Svelte 5 Snippet Compatibility
For Svelte 5 compatibility, `sveld` automatically generates optional snippet props for all slots. This allows consumers to use either the traditional slot syntax or Svelte 5's `{#snippet}` syntax.
When parsing runes components, `sveld` maps `{@render ...}` calls back into the same slot metadata used for traditional `` declarations. Reserved snippet props such as `children`, along with named snippet props discovered from `{@render ...}`, are represented through `slots` metadata and generated snippet prop types rather than duplicated in the `props` output.
Positional snippet calls such as `{@render row?.(item, index)}` are preserved as typed props when the prop itself has an explicit type like `Snippet<[Item, number]>`. They are not converted into synthetic slot metadata.
For slots with props (e.g., `let:prop`), the generated type uses a Snippet-compatible signature:
```ts
slotName?: (this: void, ...args: [{ prop: PropType }]) => void;
```
For slots without props:
```ts
slotName?: (this: void) => void;
```
**Why this signature?**
- **`this: void`** – Ensures the snippet cannot be called with a `this` context, matching Svelte's internal enforcement that snippets are pure render functions
- **`...args: [Props]`** – Uses tuple spread for type-safe parameters. This accepts fixed-length tuples (like `[{ row: Row }]`) while rejecting array types (like `Props[]`), matching how Svelte's `Snippet` type works
**Default slot (`children` prop):**
The default slot generates an optional `children` snippet prop:
```svelte
{#snippet children({ item, index })}
{item.text} (#{index})
{/snippet}
```
Generated types:
```ts
type DropdownProps = {
items: Item[];
selectedId?: string;
// Default slot as children snippet prop
children?: (this: void, ...args: [{ item: Item; index: number }]) => void;
};
```
**Named slots:**
```svelte
{#snippet cell({ cell, row })}
{#if cell.key === 'actions'}
handleAction(row)}>Edit
{:else}
{cell.value}
{/if}
{/snippet}
```
The generated TypeScript definition includes both the snippet prop and the traditional slot definition:
```ts
type DataTableProps = {
// ... other props
// Snippet prop for Svelte 5 compatibility
cell?: (
this: void,
...args: [
{
row: Row;
cell: DataTableCell;
rowIndex: number;
cellIndex: number;
},
]
) => void;
// Default slot as children prop
children?: (this: void) => void;
};
export default class DataTable extends SvelteComponentTyped<
DataTableProps,
{
/* events */
},
{
// Traditional slot definition (Svelte 3/4)
default: Record;
cell: {
row: Row;
cell: DataTableCell;
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 such as `onclick` are treated as component props, not events. The `events` output remains reserved for real 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:**
```js
/**
* Optional event description
* @event {EventDetail} eventname [inline description]
*/
```
**Example:**
**Svelte 5 Runes:**
```svelte
/**
* Fired when a value is saved.
* @event {{ id: string }} save
*/
let { onsave } = $props();
onsave?.({ id: "1" })}>Save
```
**Svelte 5 Runes with dispatched events:**
```svelte
/**
* @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");
});
```
**Svelte 3, 4, 5 (non-Runes):**
```svelte
/**
* @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");
```
Output:
```ts
export default class Component extends SvelteComponentTyped<
ComponentProps,
{
"button:key": CustomEvent<{ key: string }>;
/** Fired when `key` changes. */ key: CustomEvent;
},
Record
> {}
```
#### Using `@property` for complex event details
For events with complex object payloads, use the `@property` tag to document individual properties. The main comment description will be used as the event description.
**Signature:**
```js
/**
* Event description
* @event eventname
* @type {object}
* @property {Type} propertyName - Property description
*/
```
**Example:**
**Svelte 5 Runes:**
```svelte
/**
* 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 = "jane@example.com", newsletter = true } = $props();
import { createEventDispatcher } from "svelte";
const dispatch = createEventDispatcher();
function handleSubmit() {
dispatch("submit", { name, email, newsletter });
}
Submit
```
**Svelte 3, 4, 5 (non-Runes):**
```svelte
/**
* 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 = "jane@example.com";
export let newsletter = true;
import { createEventDispatcher } from "svelte";
const dispatch = createEventDispatcher();
function handleSubmit() {
dispatch("submit", { name, email, newsletter });
}
Submit
```
Output:
```ts
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
> {}
```
#### Optional properties in event details
Just like with typedefs, you can mark event detail properties as optional using square brackets. This is useful when some properties may not always be included in the event payload.
**Example:**
**Svelte 5 Runes:**
```svelte
/**
* 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,
});
}
Throw
```
**Svelte 3, 4, 5 (non-Runes):**
```svelte
/**
* 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,
});
}
Throw
```
Output:
```ts
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
> {}
```
### Context API
`sveld` automatically generates TypeScript definitions for Svelte's `setContext`/`getContext` API by extracting types from JSDoc annotations on the context values.
#### How it works
When you use `setContext` in a component, `sveld` will:
1. Detect the `setContext` call
2. Extract the context key (must be a string literal)
3. Find JSDoc `@type` annotations on the variables being passed
4. Generate a TypeScript type export for the context
#### Example
**Modal.svelte**
**Svelte 5 Runes:**
```svelte
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();
{@render children?.()}
```
**Svelte 3, 4, 5 (non-Runes):**
```svelte
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 });
```
**Generated TypeScript definition:**
```ts
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,
{ default: Record }
> {}
```
**Consumer usage:**
```svelte
import { getContext } from 'svelte';
import type { SimpleModalContext } from 'modal-library/Modal.svelte';
// Fully typed with autocomplete!
const { close, open } = getContext<SimpleModalContext>('simple-modal');
Close
```
#### Explicitly typing contexts
There are several ways to provide type information for contexts:
**Option 1: Inline JSDoc on variables (recommended)**
```svelte
import { setContext } from 'svelte';
/**
* @type {() => void}
*/
const close = () => {};
setContext('modal', { close });
```
**Option 2: Using @typedef for complex types**
```svelte
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 });
```
**Option 3: Referencing imported types**
```svelte
import { setContext } from 'svelte';
/**
* @type {typeof import("./types").ModalAPI}
*/
const modalAPI = {
open: () => {},
close: () => {}
};
setContext('modal', modalAPI);
```
**Option 4: Direct object literal with inline functions**
```svelte
import { setContext } from 'svelte';
// sveld infers basic function signatures
setContext('modal', {
open: (component, props) => {}, // Inferred as (arg, arg) => any
close: () => {} // Inferred as () => any
});
```
> **Note:** For best results, use explicit JSDoc `@type` annotations. Inline functions without annotations will be inferred with generic signatures.
#### 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 pick up inline HTML elements that `$$restProps` is forwarded to. However, it cannot infer the underlying element for instantiated components.
You can use the `@restProps` tag to specify the element tags that `$$restProps` is forwarded to.
**Signature:**
```js
/**
* Single element
* @restProps {tagname}
*
* Multiple elements
* @restProps {tagname-1 | tagname-2 | tagname-3}
*/
```
**Example:**
**Svelte 5 Runes:**
```svelte
import Button from "./Button.svelte";
/** @restProps {h1 | button} */
let { edit = false, children, ...restProps } = $props();
{#if edit}
{:else}
{@render children?.()}
{/if}
```
**Svelte 3, 4, 5 (non-Runes):**
```svelte
/** @restProps {h1 | button} */
export let edit = false;
import Button from "./Button.svelte";
{#if edit}
{:else}
{/if}
```
### `@extendProps`
In some cases, a component may be based on another component. The `@extendProps` tag can be used to extend generated component props.
> **Note:** `@extends` is supported as an alias but `@extendProps` is preferred to avoid conflicts with standard JSDoc `@extends` (used for class inheritance).
**Signature:**
```js
/**
* @extendProps {} ComponentProps
*/
```
**Example:**
```js
/** @extendProps {"./Button.svelte"} ButtonProps */
export const secondary = true;
import Button from "./Button.svelte";
```
### `@generics`
Currently, to define generics for a Svelte component, you must use [`generics` attribute](https://github.com/dummdidumm/rfcs/blob/bfb14dc56a70ec6ddafebf2242b8e1500e06a032/text/ts-typing-props-slots-events.md#generics) on the script tag. Note that this feature is [experimental](https://svelte.dev/docs/typescript#experimental-advanced-typings) and may change in the future.
However, the `generics` attribute only works if using `lang="ts"`; the language server will produce an error if `generics` is used without specifying `lang="ts"`.
```svelte
```
Because `sveld` is designed to support JavaScript-only usage as a baseline, the API design to specify generics uses a custom JSDoc tag `@generics`.
**Signature:**
```js
/**
* @generics {GenericParameter} GenericName
*/
```
**Example:**
```js
/**
* @generics {Row extends DataTableRow = any} Row
*/
```
**Component example:**
**Svelte 5 Runes:**
```svelte
/**
* @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();
{@render children?.({ headers, rows })}
```
**Svelte 3, 4, 5 (non-Runes):**
```svelte
/**
* @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 = [];
```
The generated TypeScript definition will resemble the following:
```ts
// Props type includes the full constraint, enabling indexed access types like Row["id"]
export type ComponentProps = {
rows?: ReadonlyArray;
};
export default class Component<
Row extends DataTableRow = any,
> extends SvelteComponentTyped<
ComponentProps,
Record,
Record
> {}
```
For a parameter list, the name should be comma-separated but not include spaces.
```js
/**
* @generics {Param1, Param2} Name1,Name2
*/
```
```ts
export type ComponentProps = { ... };
export default class Component extends SvelteComponentTyped<
ComponentProps,
Record,
Record
> {}
```
### `@component` comments
The Svelte Language Server supports component-level comments through the following syntax: ``.
`sveld` will copy these over to the exported default component in the TypeScript definition.
**Example:**
**Svelte 5 Runes:**
```svelte
let { children } = $props();
{@render children?.()}
```
**Svelte 3, 4, 5 (non-Runes):**
```svelte
```
Output:
```ts
/**
* @example
*
* Text
*
*/
export default class Button extends SvelteComponentTyped<
ButtonProps,
Record,
{ default: Record }
> {}
```
### Accessor Props
Exported functions and consts become accessor props in generated TypeScript definitions. Use `@type` to document function signatures, or use `@param` and `@returns` (or `@return`) JSDoc tags for richer documentation.
Note that `@type` tag annotations take precedence over `@param`/`@returns` tags.
**Signature:**
```js
/**
* Function description
* @param {Type} paramName - Parameter description
* @param {Type} [optionalParam] - Optional parameter
* @returns {ReturnType} Return value description
*/
```
**Example:**
**Svelte 5 Runes:**
```svelte
/**
* @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;
}
{@render children?.()}
```
**Svelte 3, 4, 5 (non-Runes):**
```svelte
/**
* @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;
}
```
Output:
```ts
export type NotificationData = {
/** Optional id for deduplication */
id?: string;
kind?: "error" | "info" | "success";
};
export type ComponentProps = Record;
export default class Component extends SvelteComponentTyped<
ComponentProps,
Record,
Record
> {
/**
* 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
Refer to the [contributing guidelines](CONTRIBUTING.md).
## License
[Apache-2.0](LICENSE)
[npm]: https://img.shields.io/npm/v/sveld.svg?color=262626&style=for-the-badge
[npm-url]: https://npmjs.com/package/sveld