Skip to content

Svelte Compiler

The Svelte compiler reads visual.components from a xtyle definition and generates Svelte 5 component files. Each component gets typed props, size and variant support, state handling, and Snippet-based content slots.

Terminal window
npm install @xtylejs/compiler-svelte
Terminal window
xtyle compile brand.xtyle.json --target svelte

Generated components are written to ./out/svelte/. Each component gets its own file named in PascalCase (e.g. Button.svelte, Panel.svelte, Input.svelte).

import { compile } from "@xtylejs/compiler-svelte";
const components = compile(definition);
for (const [filename, source] of Object.entries(components)) {
console.log(`${filename}:\n${source}`);
}

The compile function returns a Record<string, string> mapping filenames to Svelte component source code.

The compiler infers the HTML element from the component name:

Component NameHTML Element
button<button>
input<input>
select<select>
textarea<textarea>
a<a>
anything else<div>

Every generated component uses a TypeScript Props interface with $props():

<script lang="ts">
import type { Snippet } from 'svelte';
interface Props {
size?: 'sm' | 'md' | 'lg';
variant?: 'secondary';
disabled?: boolean;
onclick?: (e: MouseEvent) => void;
children?: Snippet;
}
let { size, variant, disabled = false, onclick, children }: Props = $props();
</script>

When a component defines sizes or variants, the generated component accepts size and variant props as union types. These map to BEM-style modifier classes:

<button
class="xtyle-button{size ? ` xtyle-button--${size}` : ''}{variant ? ` xtyle-button--${variant}` : ''}"
class:xtyle-button--disabled={disabled}
{disabled}
{onclick}
>

Button components use children via Svelte 5 Snippets. Div-based components with multiple anatomy parts use named Snippets for each part:

<div class="xtyle-panel">
{#if header}
<div class="xtyle-panel-header">
{@render header()}
</div>
{/if}
<div class="xtyle-panel-body">
{@render children?.()}
</div>
</div>

Input components use bind:value with $bindable() for two-way binding:

<script lang="ts">
interface Props {
size?: 'sm' | 'md' | 'lg';
disabled?: boolean;
value?: string;
placeholder?: string;
oninput?: (e: Event) => void;
}
let { size, disabled = false, value = $bindable(''), placeholder = '', oninput }: Props = $props();
</script>
<input
class="xtyle-input{size ? ` xtyle-input--${size}` : ''}"
class:xtyle-input--disabled={disabled}
{disabled}
bind:value
{placeholder}
{oninput}
/>

Given the Midnight Diner button definition, the compiler generates Button.svelte:

<script lang="ts">
import type { Snippet } from 'svelte';
interface Props {
size?: 'sm' | 'md' | 'lg';
variant?: 'secondary';
disabled?: boolean;
onclick?: (e: MouseEvent) => void;
children?: Snippet;
}
let { size, variant, disabled = false, onclick, children }: Props = $props();
</script>
<button
class="xtyle-button{size ? ` xtyle-button--${size}` : ''}{variant ? ` xtyle-button--${variant}` : ''}"
class:xtyle-button--disabled={disabled}
{disabled}
{onclick}
>
<span class="xtyle-button-label">
{@render children?.()}
</span>
</button>

The generated components use CSS classes matching the CSS compiler’s output. Pair both compilers to get styled, functional components from a single definition.

The Svelte components reference class names that the CSS compiler generates. Compile both targets together for a complete result:

Terminal window
xtyle compile brand.xtyle.json --target css svelte

Import the CSS file in your Svelte app and the generated components pick up their styles automatically.