Getting started — Svelte
Add Justin to a Svelte 5 / SvelteKit app. Six steps: authenticate to the package feed, install, import the styles, wire the icon plugin, render a component, verify.
1. Authenticate to the feed
@davidhorn/justin is published to the private shared Azure DevOps
Artifacts npm feed — a plain bun add fails until your project points at
it. Add this to the .npmrc in your project root:
@davidhorn:registry=https://pkgs.dev.azure.com/davidhorn/Packages/_packaging/shared/npm/registry/
always-auth=true Feed access and credentials are documented on the internal Justin Package Feeds page.
2. Install
bun add @davidhorn/justin The only peer dependency is svelte ^5 — the library uses runes and does
not support Svelte 4.
3. Import the styles
Once, at the root of your app — usually in src/routes/+layout.svelte:
<script>
import '@davidhorn/justin/styles.css';
</script> This registers all design tokens as CSS custom properties (--color-primary, --spacing-4, --font-size-headline-md, …) and applies the ITCSS layers
(reset, generic, elements, components, utilities). It also loads the bundled Inter variable font — the @font-face sources
resolve to woff2 files shipped inside the package, so there’s nothing extra
to install or link.
4. Wire the icon plugin
DhIcon ships the full Lucide glyph set and serves
each SVG as a per-file static asset on your own origin. Register the
Vite plugin so the icons end up under /icons/:
// vite.config.js
import { sveltekit } from '@sveltejs/kit/vite';
import { justinIcons } from '@davidhorn/justin/vite';
import { defineConfig } from 'vite';
export default defineConfig({
plugins: [sveltekit(), justinIcons()]
}); 5. Use a component
<script>
import { DhButton, DhTextInput } from '@davidhorn/justin';
let name = $state('');
</script>
<DhTextInput bind:value={name} label="Your name" />
<DhButton variant="primary" text="Submit" onclick={() => alert(name)} /> 6. Verify
Run your dev server and load the page:
- The button renders with Justin’s primary color and rounded corners. An unstyled browser button means the stylesheet import from step 3 is missing.
- Text renders in Inter — the network tab shows a request for
inter-latin-wght-normal.woff2. A system font instead means the stylesheet import from step 3 is missing or overridden. - Add
<DhIcon name="search" />to the page. A blank spot with a/icons/search.svg404 in the network tab means the Vite plugin from step 4 isn’t registered.
That’s the full setup. Everything below is optional.
Going further
Granular imports
The package exposes per-component and per-layer subpaths so you can cherry-pick
when you don’t want the whole library. When importing a component’s CSS
directly, also import @davidhorn/justin/styles/settings.css (or a per-group
file under styles/settings/) so the design-token custom properties used by
that CSS are defined.
Icon-only recipe:
<script>
// Just the icon component
import DhIcon from '@davidhorn/justin/components/Icon.svelte';
// Tokens (so the icon picks up your app's theme)
import '@davidhorn/justin/styles/settings.css';
// The icon's component CSS only
import '@davidhorn/justin/styles/components/icon.css';
</script>
<DhIcon name="search" /> Tokens-only (build your own components on top):
<script>
import '@davidhorn/justin/styles/settings.css';
// or one group at a time:
// import '@davidhorn/justin/styles/settings/colors.css';
</script> Available CSS subpaths
| Subpath | Ships |
|---|---|
@davidhorn/justin/styles.css | Everything (tokens + reset + elements + components + utilities) |
@davidhorn/justin/styles/settings.css | Tokens only |
@davidhorn/justin/styles/settings/<group>.css | One token group (colors, radius, spacing, typography, motion) |
@davidhorn/justin/styles/generic.css | Reset + box-sizing |
@davidhorn/justin/styles/elements.css | Base HTML element styles |
@davidhorn/justin/styles/components.css | All component CSS, no tokens |
@davidhorn/justin/styles/components/<name>.css | One component’s CSS |
@davidhorn/justin/styles/utilities.css | Type-scale classes (dh-display-lg … dh-label-sm) — import last |
@davidhorn/justin/styles/fonts.css | Inter @font-face only — the per-layer bundles above carry none |
Light and dark mode
By default, components follow the user’s prefers-color-scheme. To force a
theme, set data-theme="light" or data-theme="dark" on <html>. The
docs site’s theme picker uses the same key (localStorage.dh-demo-theme) —
share that mechanism in your own app if you want a manual override.
Overriding tokens
Justin ships its CSS in named cascade layers, declared in this order:
@layer justin-base, justin-overrides, generic, elements, components, utilities; Design tokens live in justin-base. To change one, put your declaration in the justin-overrides layer — declared after the base, it wins regardless of
selector specificity, with no !important and no need to out-specify the [data-theme] rules:
/* your app's CSS, loaded after '@davidhorn/justin/styles.css' */
@layer justin-overrides {
:root {
--color-primary: #0066cc;
--radius-md: 0.25rem;
}
} Import order matters: bring in @davidhorn/justin/styles.css first so the
layer order is established, then your overrides.
What’s next
- See every component and its props at Justin for Svelte.
- Browse the Foundations for the token vocabulary.
- Read the design philosophy to understand the rules components are built against.