How to Create and Publish a Svelte Component Package

A step-by-step guide to creating a reusable Svelte component library as an npm package — from project setup with sv create, configuring package.json exports, TypeScript support, and publishing to npm.

Creating and Publishing a Svelte Component Package

Building reusable UI components is one of the most valuable things you can do for your team or the open-source community. Svelte makes this particularly clean — components compile down to vanilla JavaScript with zero runtime overhead.

There are two main approaches:

  • Standalone package: A single npm package containing your component library
  • Component library monorepo: Multiple packages in one repo (e.g., with Turborepo or pnpm workspaces)

For most cases, the standalone approach is the right starting point. Here’s how to do it.

Project Setup

Use the official sv CLI to scaffold a Svelte library project:

npx sv create my-svelte-components

During the questionnaire:

  • Select “SvelteKit library” as the project type
  • Choose TypeScript (strongly recommended for consumer DX)
  • Optionally add Prettier, ESLint, TailwindCSS
  • Choose your preferred package manager (bun, pnpm, npm)

This creates a project with the correct structure for an npm-publishable library.

Project Structure

my-svelte-components/
├── src/
│   ├── lib/
│   │   ├── components/
│   │   │   ├── Button.svelte
│   │   │   ├── Modal.svelte
│   │   │   └── index.ts      ← re-export components here
│   │   └── index.ts          ← main library entry point
│   └── routes/               ← demo/docs app (not published)
│       └── +page.svelte
├── package.json
├── svelte.config.js
└── tsconfig.json

Writing Your Components

<!-- src/lib/components/Button.svelte -->
<script lang="ts">
  type Variant = 'primary' | 'secondary' | 'ghost';
  type Size = 'sm' | 'md' | 'lg';

  interface Props {
    variant?: Variant;
    size?: Size;
    disabled?: boolean;
    onclick?: () => void;
    children?: import('svelte').Snippet;
  }

  let {
    variant = 'primary',
    size = 'md',
    disabled = false,
    onclick,
    children,
  }: Props = $props();
</script>

<button
  class="btn btn-{variant} btn-{size}"
  {disabled}
  {onclick}
>
  {@render children?.()}
</button>

<style>
  .btn {
    border-radius: 6px;
    font-weight: 500;
    cursor: pointer;
    border: none;
    transition: background-color 150ms ease;
  }

  .btn-primary { background: #3b82f6; color: white; }
  .btn-primary:hover { background: #2563eb; }
  .btn-secondary { background: #e5e7eb; color: #111; }
  .btn-ghost { background: transparent; border: 1px solid currentColor; }

  .btn-sm { padding: 4px 12px; font-size: 0.875rem; }
  .btn-md { padding: 8px 16px; font-size: 1rem; }
  .btn-lg { padding: 12px 24px; font-size: 1.125rem; }

  .btn:disabled { opacity: 0.5; cursor: not-allowed; }
</style>

Setting Up Exports

Export everything from src/lib/index.ts:

// src/lib/index.ts
export { default as Button } from "./components/Button.svelte";
export { default as Modal } from "./components/Modal.svelte";
export { default as Card } from "./components/Card.svelte";

// Also export any TypeScript types
export type { ButtonProps } from "./components/Button.svelte";

Configuring package.json

The sv create tool generates most of this, but verify these key fields:

{
  "name": "@your-scope/my-svelte-components",
  "version": "0.1.0",
  "description": "A reusable Svelte component library",
  "type": "module",
  "main": "./dist/index.js",
  "svelte": "./dist/index.js",
  "types": "./dist/index.d.ts",
  "exports": {
    ".": {
      "types": "./dist/index.d.ts",
      "svelte": "./dist/index.js",
      "default": "./dist/index.js"
    }
  },
  "files": ["dist"],
  "scripts": {
    "build": "vite build && tsc --emitDeclarationOnly",
    "dev": "vite dev",
    "preview": "vite preview",
    "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json"
  },
  "peerDependencies": {
    "svelte": "^5.0.0"
  },
  "devDependencies": {
    "@sveltejs/adapter-auto": "^3.0.0",
    "@sveltejs/kit": "^2.0.0",
    "@sveltejs/package": "^2.0.0",
    "svelte": "^5.0.0",
    "svelte-check": "^4.0.0",
    "typescript": "^5.0.0",
    "vite": "^6.0.0"
  }
}

The "svelte" field tells bundlers that understand Svelte to use the Svelte-specific build. The "exports" map handles modern Node.js module resolution.

Building the Library

# Build the library (outputs to /dist)
bun run build

# Type-check everything
bun run check

The build will output compiled .js files and .d.ts type declarations to /dist.

Local Development and Testing

Test your components in the local demo app (src/routes/+page.svelte) before publishing:

<!-- src/routes/+page.svelte -->
<script>
  import { Button, Card } from '$lib';
</script>

<Button variant="primary" onclick={() => alert('clicked!')}>
  Click Me
</Button>

<Button variant="secondary" size="lg">
  Large Secondary
</Button>

Run the dev server:

bun dev

Publishing to npm

# Log in to npm
npm login

# Build the library
bun run build

# Publish (scoped packages are private by default — use --access public for open source)
npm publish --access public

For a first release, version 0.1.0 is conventional. Use semantic versioning:

  • Patch (0.1.1): Bug fixes, no API changes
  • Minor (0.2.0): New features, backward compatible
  • Major (1.0.0): Breaking changes

Using Your Published Library

Once published, consumers install and use it like any other package:

npm install @your-scope/my-svelte-components
<script>
  import { Button, Card } from '@your-scope/my-svelte-components';
</script>

<Button variant="primary">Hello from the library!</Button>

Because it exports proper Svelte components (not pre-compiled), consumers get full Svelte optimization, tree-shaking, and TypeScript autocomplete.

Tips for a Good Component Library

  • Document with JSDoc — TypeScript consumers see docs in their IDE
  • Provide sensible defaults — every prop should have a reasonable default
  • Support slots/snippets — composable components are more useful
  • Export TypeScript types — makes consuming the library much better
  • Write a changelog — consumers need to know what changed between versions
  • Set up a GitHub Actions CI — run svelte-check on every PR

💬 Want to learn, build, and grow with a community of developers? Join the King Technologies Discord — where code meets community!