Setting up a project
Setup has four steps: requirements, install, CSS, and the Root layout. The sections below come straight from the Getting Started reference.
Requirements
- Svelte 5
- SvelteKit
- Tailwind CSS 4
Svelte and SvelteKit are declared peer dependencies. Tailwind CSS 4 is part of the consuming application's styling setup. The library declares lucide-svelte and tailwind-merge as dependencies; if your application imports either directly, declare it in your application's dependencies too.
Optional: prose components (content/prose)
The ProseMarkdown, ProsePage, ProseCallout and related components render Tailwind typography classes. To use them, install @tailwindcss/typography and add it to your CSS:
npm install @tailwindcss/typography
@plugin "@tailwindcss/typography";
Without this plugin the prose components will render without typographic styling.
Optional: code highlighting (DocShowCode, BlockViewCode)
DocShowCode and BlockViewCode use svelte-highlight for syntax highlighting. The library ships the highlighter logic but not the theme CSS — you must import a theme yourself. If importing svelte-highlight directly, declare it in your application's dependencies:
import 'svelte-highlight/styles/github-dark.css';
Available themes are listed in the svelte-highlight documentation.
Installation
npm install @atom-forge/ui
CSS setup
First integrate Tailwind CSS 4 into your application's build (for example, install tailwindcss and @tailwindcss/vite and register the Tailwind plugin in vite.config.ts, alongside the SvelteKit plugin). Installing @atom-forge/ui alone does not process Tailwind directives.
Then add these lines to your app's CSS entry point (src/app.css):
@import "tailwindcss";
@import "../node_modules/@atom-forge/ui/dist/core/theme.css";
@source "../node_modules/@atom-forge/ui/dist";
@source ".";
The @source directive for node_modules is required because Tailwind CSS 4 does not scan node_modules by default. Without it, the utility classes used inside atom-forge components won't be included in your bundle.
@atom-forge/ui/dist/core/theme.css defines the semantic color tokens, surface layers, and utility classes that all components depend on.
Layout setup
Import your CSS and wrap the root layout with <Root>. This single component:
- Registers all overlay managers (Modal, Toast, Drawer, Popup)
- Provides theme state and persists changes to
localStorage; initial preference detection requires the app-level script below
- Renders portal targets and overlay containers
<script lang="ts">
import '../app.css';
import { Root } from '@atom-forge/ui';
let { children } = $props();
</script>
<Root>
{@render children()}
</Root>
Root props
| Prop |
Type |
Default |
Description |
children |
Snippet |
— |
Content rendered beneath the theme and overlay providers. |
manageBodyStyle |
boolean |
true |
Applies the canvas background and text colors to document.body. |
Root has no dark or light props. On mount it initializes theme state from the <html> element's dark class, not directly from saved preferences or prefers-color-scheme. Install the pre-load script below to apply those preferences before Root mounts; otherwise an initially unclassed document starts light.
Dark mode
getThemeManager() is available anywhere inside the <Root> tree. Bind theme.dark directly:
<script lang="ts">
import { getThemeManager, Switch } from '@atom-forge/ui';
import { Moon, Sun } from 'lucide-svelte';
const theme = getThemeManager();
</script>
<Switch bind:value={theme.dark} icons={{ on: Moon, off: Sun }}/>
Preventing flash on load
Without extra steps, users in dark mode will briefly see a white page before the saved preference is applied — a classic FOUC. To eliminate this, add a small blocking script to app.html before %sveltekit.head%. It reads localStorage synchronously and adds the dark class to <html> before the first paint:
<head>
<script>
(function() {
try {
var saved = localStorage.getItem('dark');
if (saved !== null) {
if (JSON.parse(saved)) document.documentElement.classList.add('dark');
} else if (window.matchMedia('(prefers-color-scheme: dark)').matches) {
document.documentElement.classList.add('dark');
}
} catch (e) {}
})();
</script>
%sveltekit.head%
</head>
The script is intentionally render-blocking (no async/defer) so it always runs before any content is painted. The try/catch handles environments where localStorage is unavailable (e.g. private browsing with strict settings).
Importing components
All components are exported from the root package:
import { Button, Input, Card, Select, Switch } from '@atom-forge/ui';
Alternative: the UI namespace
The flat UI object provides the same public components and UI manager functions under a single import. It references the existing implementations; named exports remain available and can be mixed with namespace usage.
<script lang="ts">
import { UI } from '@atom-forge/ui';
let enabled = $state(false);
</script>
<UI.Root>
<UI.Button label="Save"/>
<UI.Checkbox label="Enabled" bind:value={enabled}/>
</UI.Root>
Context-bound APIs are also available, including UI.getThemeManager(), UI.getModalManager(), UI.getDrawerManager(), UI.getPopupManager(), UI.getToastManager(), UI.getCheckboxGroupManager(), and UI.getBlockAPI(). Existing manager creation and context setter functions are included as well.
Retrieve managers during initialization of a component inside the appropriate provider (Root, CheckboxGroupManager, or BlockEditor). Then call the retrieved manager from event handlers. The namespace does not create global manager instances or change context requirements.
<script lang="ts">
import { UI } from '@atom-forge/ui';
const toast = UI.getToastManager();
</script>
<UI.Button label="Notify" onclick={() => toast.show('File saved!', { type: 'success' })}/>
For dialogs, retrieve const modal = UI.getModalManager() during initialization, then call the existing await modal.open(DialogComponent, props) API from an event handler. Drawer and popup managers work the same way as their named-export counterparts.
Types, general-purpose helpers (such as debounce), and utility namespaces remain separate named exports. There is no UI.Modal component: the existing modal API consists of UI.ModalContainer and the modal manager functions.
The namespace's properties are readonly in TypeScript and preserve the original component and function types. Because the object references the entire component collection, namespace usage may retain more code than direct named imports. Prefer named imports when minimizing bundle size is important; equivalent tree-shaking is not guaranteed.
Setting up a project
Setup has four steps: requirements, install, CSS, and the
Rootlayout. The sections below come straight from the Getting Started reference.Requirements
Svelte and SvelteKit are declared peer dependencies. Tailwind CSS 4 is part of the consuming application's styling setup. The library declares
lucide-svelteandtailwind-mergeas dependencies; if your application imports either directly, declare it in your application's dependencies too.Optional: prose components (
content/prose)The
ProseMarkdown,ProsePage,ProseCalloutand related components render Tailwind typography classes. To use them, install@tailwindcss/typographyand add it to your CSS:/* src/app.css */ @plugin "@tailwindcss/typography";Without this plugin the prose components will render without typographic styling.
Optional: code highlighting (
DocShowCode,BlockViewCode)DocShowCodeandBlockViewCodeusesvelte-highlightfor syntax highlighting. The library ships the highlighter logic but not the theme CSS — you must import a theme yourself. If importingsvelte-highlightdirectly, declare it in your application's dependencies:// e.g. in your layout or app entry import 'svelte-highlight/styles/github-dark.css';Available themes are listed in the
svelte-highlightdocumentation.Installation
CSS setup
First integrate Tailwind CSS 4 into your application's build (for example, install
tailwindcssand@tailwindcss/viteand register the Tailwind plugin invite.config.ts, alongside the SvelteKit plugin). Installing@atom-forge/uialone does not process Tailwind directives.Then add these lines to your app's CSS entry point (
src/app.css):@import "tailwindcss"; /* 1. Import semantic tokens and base styles */ @import "../node_modules/@atom-forge/ui/dist/core/theme.css"; /* 2. Tell Tailwind to scan the library for utility classes */ @source "../node_modules/@atom-forge/ui/dist"; /* 3. Ensure your own project files are scanned */ @source ".";The
@sourcedirective fornode_modulesis required because Tailwind CSS 4 does not scannode_modulesby default. Without it, the utility classes used inside atom-forge components won't be included in your bundle.@atom-forge/ui/dist/core/theme.cssdefines the semantic color tokens, surface layers, and utility classes that all components depend on.Layout setup
Import your CSS and wrap the root layout with
<Root>. This single component:localStorage; initial preference detection requires the app-level script below<!-- src/routes/+layout.svelte --> <script lang="ts"> import '../app.css'; import { Root } from '@atom-forge/ui'; let { children } = $props(); </script> <Root> {@render children()} </Root>Root props
childrenSnippetmanageBodyStylebooleantruedocument.body.Root has no
darkorlightprops. On mount it initializes theme state from the<html>element'sdarkclass, not directly from saved preferences orprefers-color-scheme. Install the pre-load script below to apply those preferences before Root mounts; otherwise an initially unclassed document starts light.Dark mode
getThemeManager()is available anywhere inside the<Root>tree. Bindtheme.darkdirectly:<script lang="ts"> import { getThemeManager, Switch } from '@atom-forge/ui'; import { Moon, Sun } from 'lucide-svelte'; const theme = getThemeManager(); </script> <Switch bind:value={theme.dark} icons={{ on: Moon, off: Sun }}/>Preventing flash on load
Without extra steps, users in dark mode will briefly see a white page before the saved preference is applied — a classic FOUC. To eliminate this, add a small blocking script to
app.htmlbefore%sveltekit.head%. It readslocalStoragesynchronously and adds thedarkclass to<html>before the first paint:<!-- src/app.html --> <head> <!-- Add this BEFORE %sveltekit.head% --> <script> (function() { try { var saved = localStorage.getItem('dark'); if (saved !== null) { if (JSON.parse(saved)) document.documentElement.classList.add('dark'); } else if (window.matchMedia('(prefers-color-scheme: dark)').matches) { document.documentElement.classList.add('dark'); } } catch (e) {} })(); </script> %sveltekit.head% </head>The script is intentionally render-blocking (no
async/defer) so it always runs before any content is painted. Thetry/catchhandles environments wherelocalStorageis unavailable (e.g. private browsing with strict settings).Importing components
All components are exported from the root package:
import { Button, Input, Card, Select, Switch } from '@atom-forge/ui';Alternative: the UI namespace
The flat
UIobject provides the same public components and UI manager functions under a single import. It references the existing implementations; named exports remain available and can be mixed with namespace usage.<script lang="ts"> import { UI } from '@atom-forge/ui'; let enabled = $state(false); </script> <UI.Root> <UI.Button label="Save"/> <UI.Checkbox label="Enabled" bind:value={enabled}/> </UI.Root>Context-bound APIs are also available, including
UI.getThemeManager(),UI.getModalManager(),UI.getDrawerManager(),UI.getPopupManager(),UI.getToastManager(),UI.getCheckboxGroupManager(), andUI.getBlockAPI(). Existing manager creation and context setter functions are included as well.Retrieve managers during initialization of a component inside the appropriate provider (
Root,CheckboxGroupManager, orBlockEditor). Then call the retrieved manager from event handlers. The namespace does not create global manager instances or change context requirements.<!-- A child component rendered inside UI.Root --> <script lang="ts"> import { UI } from '@atom-forge/ui'; const toast = UI.getToastManager(); </script> <UI.Button label="Notify" onclick={() => toast.show('File saved!', { type: 'success' })}/>For dialogs, retrieve
const modal = UI.getModalManager()during initialization, then call the existingawait modal.open(DialogComponent, props)API from an event handler. Drawer and popup managers work the same way as their named-export counterparts.Types, general-purpose helpers (such as
debounce), and utility namespaces remain separate named exports. There is noUI.Modalcomponent: the existing modal API consists ofUI.ModalContainerand the modal manager functions.The namespace's properties are readonly in TypeScript and preserve the original component and function types. Because the object references the entire component collection, namespace usage may retain more code than direct named imports. Prefer named imports when minimizing bundle size is important; equivalent tree-shaking is not guaranteed.