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.
bun add @joshicodes/cock2. 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.
@import 'tailwindcss';
@import '@joshicodes/cock/theme.css';If your bundler does not resolve the package path, point Tailwind at the folder yourself:
@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
export { themeHandle as handle } from '@joshicodes/cock';Already have a handle? Put it in a sequence:
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, inside <head> -->
<script>%cock.theme%</script>import { themeScript } from '@joshicodes/cock';
export const handle = async ({ event, resolve }) =>
resolve(event, {
transformPageChunk: ({ html }) => html.replace('%cock.theme%', themeScript)
});It only matters for the first paint
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:
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, 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.
<script lang="ts">
import { Toaster } from '@joshicodes/cock';
let { children } = $props();
</script>
{@render children()}
<Toaster position="bottom-right" />5. Use it
<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.