Skip to content

Getting started

Five minutes from an empty SvelteKit project to working components. You need Svelte 5 and Tailwind 4; everything else comes with the package.

Svelte
^5.0.0
Tailwind CSS
^4.0.0
Node
>=18

Svelte and Tailwind are peer dependencies, so your project keeps control of the exact versions. The Node range only applies to the SvelteKit server; the components themselves run wherever Svelte does.

1. Install

The package is on the public npm registry. Its only runtime dependencies are clsx and tailwind-merge.

terminal
bun add @joshicodes/cock

2. Point Tailwind at the tokens

Add one import to the stylesheet that already pulls in Tailwind. It brings the design tokens, the dark variant, the base layer, and a @source directive so Tailwind sees the class names used inside the package.

src/app.css
@import 'tailwindcss';
@import '@joshicodes/cock/theme.css';

If your bundler does not resolve the package path, point Tailwind at the folder yourself:

src/app.css
@import 'tailwindcss';
@source '../node_modules/@joshicodes/cock/dist';
@import '@joshicodes/cock/theme.css';

3. Fix the dark mode flash

Dark mode is a dark class on <html>, and the stored preference lives in local storage. A server cannot read local storage, so without help the page renders in the wrong scheme and corrects itself one frame later — the white flash every dark site starts out with.

The kit ships a hook that writes the class before anything paints. One line, no changes to app.html:

src/hooks.server.ts
// src/hooks.server.ts
export { themeHandle as handle } from '@joshicodes/cock';

Already have a handle? Put it in a sequence:

src/hooks.server.ts
import { sequence } from '@sveltejs/kit/hooks';
import { themeHandle } from '@joshicodes/cock';

export const handle = sequence(themeHandle, myOwnHandle);

Somewhere other than SvelteKit

themeScript is the plain source and themeScriptTag is the same thing wrapped in a script tag. Inline either one in your template, as early in the head as you can manage. If you would rather control the position inside app.html, put a placeholder there and replace it yourself:

src/app.html
<!-- src/app.html, inside <head> -->
<script>%cock.theme%</script>
src/hooks.server.ts
import { themeScript } from '@joshicodes/cock';

export const handle = async ({ event, resolve }) =>
	resolve(event, {
		transformPageChunk: ({ html }) => html.replace('%cock.theme%', themeScript)
	});

If you send a Content-Security-Policy

The script is inline, so a policy without 'unsafe-inline' drops it. Nothing breaks loudly — you get a console warning and the flash back. Allow it by its hash:

response header
Content-Security-Policy: script-src 'self' 'sha256-71/Qn15EvQHio7Mn22AzqY0sq1GvCCFs+nkUDZeD6Mg='

That hash is this version's script, printed from the package at build time. It changes when the script does, which is rare but does happen on an update, so re-read it here when you upgrade.

A nonce works too, but only if you put the script in app.html yourself, because SvelteKit cannot nonce a tag a hook injected afterwards:

src/app.html
<!-- src/app.html, inside <head> -->
<script nonce="%sveltekit.nonce%">%cock.theme%</script>

With csp.mode: 'nonce' in svelte.config.js, SvelteKit fills the placeholder and adds the nonce to the header. The catch is that a nonce has to be new on every request, so a page carrying one cannot be prerendered — SvelteKit refuses the build with cannot use prerendering if page template contains %sveltekit.nonce%. On a prerendered site, use the hash.

4. Mount the toaster

Only needed if you want toasts. One instance in the root layout serves the whole app, and toast() works from anywhere, including outside components.

src/routes/+layout.svelte
<script lang="ts">
	import { Toaster } from '@joshicodes/cock';

	let { children } = $props();
</script>

{@render children()}

<Toaster position="bottom-right" />

5. Use it

src/routes/+page.svelte
<script lang="ts">
	import { Button, Card, CardContent, CardHeader, CardTitle, toast } from '@joshicodes/cock';
</script>

<Card class="max-w-sm">
	<CardHeader>
		<CardTitle>It works</CardTitle>
	</CardHeader>
	<CardContent>
		<Button onclick={() => toast.success('Hello')}>Say hello</Button>
	</CardContent>
</Card>

Editors

The package ships a .d.ts next to every component, so props, variants and their defaults complete themselves — as long as your editor speaks Svelte. In VS Code that is the official Svelte extension. In WebStorm and the other JetBrains IDEs it is the bundled Svelte plugin.

If WebStorm marks the import as unresolved and offers nothing, it is usually the IDE reading the conditional exports map rather than anything in your project — see WEB-75046, fixed in the 2025.3.1 builds. The package lists a plain default entry alongside the svelte one for that reason. On an older build, updating and then File → Invalidate Caches is the fix.

Where to go next

  • Theming — the three colours everything else is mixed from, and the runtime API.
  • Components — every component with examples, props and keyboard behaviour.
  • Helpers and actions — the exports that are not components: cn, hotkeys, confetti and the four actions.
  • Types — every exported type, written out in full.
  • Theme generator — roll a palette and copy the CSS.

cock. Yes, it stands for core component kit. We checked twice.