Skip to content

Svelte

@ecss/svelte-adapter generates typed Svelte components (4 and 5) from @block and @element declarations. The adapter's id is 'svelte'.

Unlike the React/Vue adapters, the Svelte adapter is module-based: it generates real .svelte files into the .ecss/svelte/ directory, and imports of ./X.ecss are redirected to them automatically by the ECSS plugin. This is transparent to you — the component is imported from the .ecss file as usual.

Installation

sh
npm i -D @ecss/svelte-adapter
sh
pnpm add -D @ecss/svelte-adapter
sh
yarn add -D @ecss/svelte-adapter

Requires svelte version 4 or higher to be installed.

Setup

The adapter is registered in ecss.config.ts via defineConfig from @ecss/config — the config is not tied to any particular bundler; it is read by the ECSS plugin of your bundler. defaultAdapter sets the adapter applied to .ecss files by default:

ts
// ecss.config.ts
import { defineConfig } from '@ecss/config';
import { svelteAdapter } from '@ecss/svelte-adapter';

export default defineConfig({
  adapters: [svelteAdapter()],
  defaultAdapter: 'svelte',
});

Usage

Each @block turns into a Svelte component that is imported directly from the .ecss file. The component name is formed as {prefix}{BlockName} (the default prefix is E): @block ButtonEButton. Content is passed through the default slot.

svelte
<script lang="ts">
  import { EButton } from './Button.ecss';
</script>

<EButton as="button" params={{ variant: 'primary' }} onclick={onClick}>
  Click me
</EButton>

The params prop

The block's parameters are passed through a single params prop. Their types come from the generated {Block}Params interface (for example ButtonParams), so you cannot pass a non-existent @enum value.

params is required if the block has at least one required @param (without ?); if all parameters are optional or there are none at all, the prop can be omitted.

svelte
<EButton params={{ variant: 'primary' }} /> <!-- variant is required -->
<ECard />                                    <!-- Card has all parameters optional -->

The as prop

By default the component renders as a <div>. The as prop accepts any HTML tag and changes the root element. In Svelte 5 the type narrows by the chosen tag (via generics + SvelteHTMLElements), so the corresponding attributes are available:

svelte
<EButton as="button" type="submit" params={{ variant: 'primary' }}>Submit</EButton>

<EButton as="a" href="/about" params={{ variant: 'ghost' }}>Link</EButton>

With as="a", href, target, and other <a> attributes are available; with as="button", type, disabled, and so on.

class, style, and attributes

ECSS sets the block's class, the CSS variables from params, and data attributes on the root element. What the consumer passes is merged with these values:

  • class — the consumer's class is added to the block's class without overriding it;
  • style — added to the CSS variables generated from params;
  • the remaining attributes and handlers (id, aria-*, onclick, href, …) are forwarded to the root element and win on collision with data attributes.

Sub-components (@element)

If a @block contains @element, they become nested components through static properties of the root and are available in the template via dot notation:

svelte
<EButton params={{ withIcon: true }}>
  <EButton.Icon>
    <svg><!-- … --></svg>
  </EButton.Icon>
  <EButton.Text>Click me</EButton.Text>
</EButton>

Sub-components support the same as prop (the default is <div>) but do not accept params.

Options

The factory's only option is componentNamePrefix (the component name prefix, default 'E'): svelteAdapter({ componentNamePrefix: 'My' })MyButton. For more details, see the adapters overview.

Svelte version support

The adapter detects the major version of Svelte installed in the project and generates the corresponding code:

  • Svelte 5 — components use runes ($props, $derived), the slot via {@render children?.()}; events are regular attributes (onclick, oninput, …).
  • Svelte 4export let + reactive declarations $:, the slot via <slot />; all events (on:click, on:input, …) are automatically forwarded to the root element.

The version is read from svelte/package.json relative to the project root and cached. If Svelte cannot be resolved, the adapter prints a warning and uses Svelte 5 as the default.

See also