@svebcomponents/auto-options
v0.2.0
Published
Infer Svelte custom element prop options from `$props()`.
Readme
Infer Svelte custom element prop options from $props().
Svelte custom elements need prop metadata to expose component props as HTML attributes. Writing that metadata by hand gets repetitive, especially when the same information already exists in your TypeScript props.
@svebcomponents/auto-options is a build plugin that reads a Svelte component's instance script, infers prop names and primitive prop types from $props(), and injects or updates <svelte:options customElement={...} />.
Example
<script lang="ts">
interface Props {
favoriteNumber: number;
}
let props: Props = $props();
</script>
<h1>Favorite number: {props.favoriteNumber}</h1>is transformed before the Svelte compiler runs:
<svelte:options
customElement={{
props: {
favoriteNumber: {
attribute: "favorite-number",
reflect: true,
type: "Number",
},
},
}}
/>
<script lang="ts">
interface Props {
favoriteNumber: number;
}
let props: Props = $props();
</script>
<h1>Favorite number: {props.favoriteNumber}</h1>The generated attribute name is kebab-cased, so consumers can use:
<favorite-number favorite-number="42"></favorite-number>Usage
With @svebcomponents/build
If you build with @svebcomponents/build, no extra setup is needed. The generated tsdown config already runs @svebcomponents/auto-options before compiling Svelte.
Manual Usage
Install the package:
pnpm add -D @svebcomponents/auto-optionsAdd the plugin before the Svelte plugin in your Vite config:
import autoOptions from "@svebcomponents/auto-options";
import { svelte } from "@sveltejs/vite-plugin-svelte";
import { defineConfig } from "vite";
export default defineConfig({
plugins: [
autoOptions(),
svelte({
compilerOptions: {
customElement: true,
},
}),
],
});Then expose the compiled custom element from your package entrypoint. Outside @svebcomponents/build's pipeline you don't get its automatic registration guard, so guard it by hand:
import Component from "./Component.svelte";
if (!customElements.get("favorite-number") && Component.element) {
customElements.define("favorite-number", Component.element);
}
export default Component;auto-options generates the prop metadata Svelte needs for attribute conversion, but the component still has to be compiled and registered as a custom element. Without that custom element output, attributes such as favorite-number="42" will stay strings.
Declare the tag directly in the component with the string-shorthand form, and auto-options expands it into the object form with the inferred props merged in:
<svelte:options customElement="favorite-number" />The object form works too — use it directly when you also need shadow or a manual extend:
<svelte:options
customElement={{
tag: "favorite-number",
}}
/>What Gets Inferred
The plugin looks for a variable declaration initialized from $props() in the component instance script.
<script lang="ts">
let props: Props = $props();
</script>It can infer prop names and custom element types from:
| Svelte prop type | Generated custom element type |
| -------------------------- | -------------------------------------- |
| string | "String" |
| number | "Number" |
| boolean | "Boolean" |
| string/number/bool literal | "String", "Number", or "Boolean" |
| SomeType[] | "Array" |
| Array<SomeType> | "Array" |
| object type literals | "Object" |
| Record<...> | "Object" |
| interface references | "Object" |
Props without TypeScript type information are still added, but default to "String" because HTML attributes are strings by default.
Supported Prop Shapes
Inline prop types:
<script lang="ts">
let props: { count: number } = $props();
</script>Type aliases:
<script lang="ts">
type Props = {
count: number;
};
let props: Props = $props();
</script>Interfaces:
<script lang="ts">
interface Props {
count: number;
}
let props: Props = $props();
</script>Destructured props:
<script lang="ts">
let { count, ...rest }: { count: number } = $props();
</script>Untyped destructured props:
<script>
let { label } = $props();
</script>Existing Options
Manual custom element options are treated as the highest-priority source of truth.
<svelte:options
customElement={{
props: {
count: { type: "String", attribute: "data-count" },
},
}}
/>If the plugin later infers count as a number, the existing type and attribute values are preserved. Missing fields and newly discovered props are still filled in.
Use this as the escape hatch when inference is wrong or incomplete. You can manually define one prop, several props, or the entire props object; the plugin will preserve the fields you wrote and infer the rest where it can.
Defaults
For every inferred prop, the plugin generates:
attribute: the kebab-cased prop namereflect:truetype: the inferred Svelte custom element type, or"String"when no type can be resolved
Current Limitations
- Only Svelte 5
$props()declarations are inspected. - The bare
customElementboolean shorthand, and a dynamically-interpolated string tag (e.g.customElement="{x}"), aren't supported — use a plain string literal tag or the object form. - Imported prop types are not resolved. Type aliases and interfaces must be declared in the same component instance script to be inspected.
- Generic and complex TypeScript types are not fully resolved. Unknown types fall back to
"String"unless they are interface references, which are treated as"Object".
