SVG
Importing an .svg file usually hands you a URL or a component built for another framework’s runtime. The SVG plugin compiles the file into an elements-kit component instead, so the root <svg> is a real element you can pass props to.
import { defineConfig } from "vite";import elementsKitSvg from "elements-kit/integrations/svg";
export default defineConfig({ plugins: [elementsKitSvg()],});Using Astro? Add it to vite.plugins in astro.config yourself — the Astro integration does not register it. Keep enforce: "pre" (the plugin sets it) so the ?ek id is claimed before Astro’s asset pipeline sees the file.
Import with ?ek
The query is what claims the file. A bare .svg import keeps whatever meaning your framework gives it, so existing asset imports keep working:
import Close from "@material-symbols/svg-300/outlined/close.svg?ek";
<button class="unset x-button" aria-label="Close" on:click={dismiss}> <Close class="icon" /></button>;TypeScript doesn’t know the ?ek suffix, so declare it once in your project’s env.d.ts:
declare module "*.svg?ek" { const Component: ( props?: import("elements-kit/jsx-runtime").JSX.IntrinsicElements["svg"], ) => SVGSVGElement; export default Component;}The root takes props
The file’s root <svg> is built through the JSX runtime like any other element, so every prop namespace works on it — class, style:, on:, ref, and reactive values:
const tint = signal("red");
<Close fill={tint} class:active={isOpen} on:click={dismiss} />;Props win over the file’s own attributes, so a fill prop overrides a fill baked into the source. children is the exception: the file’s markup always wins, and a children prop is ignored rather than blanking the icon.
Normalization
By default the plugin drops the root’s width and height and sets fill="currentColor" unless the file already paints itself. An icon then sizes with the font and follows text color:
.icon { width: 1em; height: 1em;}viewBox is always preserved — without it there is no aspect ratio to scale by. Turn the pass off for artwork that carries its own palette and intrinsic size:
elementsKitSvg({ normalize: false });Server rendering
Nothing to configure. The generated module builds its tree through the JSX runtime, which dispatches to whichever renderer is active, so the same import works under renderToString and hydrates without rebuilding the icon.
The plugin emits that region with ns="svg", so the interior parses in the SVG namespace and paints. It knows the namespace at build time, which is why nothing needs passing at the call site.
See also
- Astro — islands; register this plugin there by hand
- Vite — the HMR plugin that ships beside it
- JSX & Elements — prop namespaces the root accepts