Skip to main content
DOCS

Documentation.

Reference for designers, developers, and anyone using the platform.

Getting your package

Your generated npm package is available as soon as you publish a release. Find the install command in Settings under the Developer tab. The package name is the one you configured there.

bash
pnpm add @your-org/ui  # or npm install / yarn add
The exact package name is in Settings. Replace @your-org/ui with the name shown there.
Tutorial: Consuming the package on the Zaklad blog walks through this in depth.

Versioning and updates

Each time the design system owner publishes a release, a new version of the package is produced with a semantic version number (a patch, minor, or major bump chosen at publish time). You consume updates exactly like any other dependency: change the version in your package.json and reinstall.

bash
pnpm add @your-org/ui@latest   # or pin an exact version, e.g. @your-org/[email protected]

Every version is a frozen snapshot, so a given version number always resolves to the same tokens, components, and styles. Nothing changes underneath you until you choose to move to a newer version, and the platform keeps a full release history with a change summary per version.

If registry publishing is not enabled for a project, each release still produces a downloadable build (a standard package tarball) that you can install from a local path or vendor into your repository. The package contents are identical either way.

Provider setup

Wrap the root of your application with the provider. It handles all the CSS for you; every token is injected and resolved automatically. Anything rendered inside the provider can use your design system tokens directly. On web, import the fonts CSS once at the app root so the @font-face rules apply.

tsx
// Web only: import once at the app root
import "@your-org/ui/fonts.css";

import { ZakladProvider } from "@your-org/ui";

function App() {
  return (
    <ZakladProvider>
      {/* your app */}
    </ZakladProvider>
  );
}

The provider works with no props; it uses your default theme automatically. To set or change the active theme, pass a theme prop using the exported Themes enum, whose members are your theme slugs (for example Themes.dark). The enum contains exactly the themes you have configured, so values are checked at compile time.

On paid tiers the provider name reflects your organisation: for example, AcmeProvider. The exact name, all available theme values, and a ready-to-copy setup snippet are generated in the README included with every npm release.

Fonts

How fonts are delivered depends on your tier.

Paid tiers (CDN, optional)

CDN font delivery is included on all paid tiers but is optional. When enabled, the fonts.css file references hosted URLs and no font bytes are bundled. If you prefer to self-host (for example, to satisfy strict content security policies or avoid the external dependency), you can opt out and the fonts will be bundled instead.

Free tier (self-hosted)

Font files are bundled directly into the package. The fonts.css rules reference the bundled files, so you host the fonts yourself. The import is identical in both cases; the difference is invisible to your code.

On Expo (managed or bare with Expo modules), no font setup is needed. The provider calls loadFonts() on mount and gates render on the result, so text components fall back to system fonts until loading completes. TTF files are always bundled under fonts/ and registered via expo-font automatically.

Using components

Components use namespaced variants so your IDE gives you the full set of valid values with autocompletion. The component itself doubles as the namespace, so you can reference variants directly on the import.

tsx
import { Button } from "@your-org/ui";

function Example() {
  return (
    <>
      {/* Shorthand: variant name directly on the component */}
      <Button.Primary onClick={() => save()}>
        Save changes
      </Button.Primary>

      {/* Or via the VARIANT enum for dynamic usage */}
      <Button
        variant={Button.VARIANT.Secondary}
        disabled
        onClick={() => cancel()}
      >
        Cancel
      </Button>
    </>
  );
}

Each component is its own namespace. Sub-components, variant enums, and prop types are all attached directly to the component you import. One import gives you access to the whole component surface with no hunting through sub-packages.

tsx
import { Button } from "@your-org/ui";

// Sub-components
Button.Primary
Button.Secondary

// Variant enum for dynamic usage
Button.VARIANT.Secondary

// TypeScript types
Button.Props
The README included with every release contains per-component documentation: available variants, props, and usage examples. Full auto-generated cross-platform docs are planned for a future release.

Using tokens directly

All token objects are exported from a single tokens export. Destructure what you need and apply values directly to any React element via inline styles.

tsx
import { tokens } from "@your-org/ui";

const { color, spacing, foundation } = tokens;

function Card() {
  return (
    <div style={{
      color: color.text.default,
      padding: spacing.padding.default,
      borderColor: color.border.default,
    }}>
      Custom element
    </div>
  );
}

color and spacing are semantic tokens: CSS variables that update automatically when the active theme changes. foundation gives you the raw palette values, which are theme-invariant. All are fully typed; your IDE will suggest paths and show the resolved default-theme value for each token as a hint.

Because semantic tokens resolve to live CSS variables, elements that use color or spacing must render inside the provider. foundation values are plain literals, so they work anywhere.

Switching themes

Every theme you have configured is exported as a member of the Themes enum, keyed by its slug (lowercase, for example Themes.dark or Themes["high-contrast"]). Your IDE will suggest the available values and flag invalid ones at compile time; the enum updates with every release as you add, rename, or remove themes.

tsx
import { ZakladProvider, Themes } from "@your-org/ui";

// Pass a theme at the root — everything inside inherits it
<ZakladProvider theme={Themes.dark}>
  {/* ... */}
</ZakladProvider>

// Or switch at runtime — store the active theme in state
const [activeTheme, setActiveTheme] = useState(Themes.light);

<ZakladProvider theme={activeTheme}>
  <button onClick={() => setActiveTheme(Themes.dark)}>
    Switch to dark
  </button>
</ZakladProvider>

The provider defaults to the theme you marked as default in the Themes editor. Passing no theme prop is the same as passing the default explicitly.

To render a region in a different theme from the rest of the app — an inverted box, a dark hero, a preview card — wrap it in the Theme component (singular) and pass the same Themes enum to its name prop. Everything inside resolves to that theme, no matter the active app theme.

tsx
import { ZakladProvider, Theme, Themes } from "@your-org/ui";

<ZakladProvider theme={Themes.light}>
  {/* this region is always dark, even though the app is light */}
  <Theme name={Themes.dark}>
    <Hero />
  </Theme>
</ZakladProvider>
Tutorial: Themes and modes on the Zaklad blog walks through this in depth.

Text styles

Text styles are exported as a namespaced Text component. Using <Text> with no variant defaults to <Text.BodyDefault>. All text components are automatically responsive: font size, line height, and spacing step up or down with the active XS–XL tier unless you override it.

tsx
import { Text } from "@your-org/ui";

// <Text> with no variant is shorthand for <Text.BodyDefault>
<Text>The quick brown fox jumps over the lazy dog.</Text>

<Text.BodyDefault>The quick brown fox jumps over the lazy dog.</Text.BodyDefault>
<Text.BodySmall>The quick brown fox jumps over the lazy dog.</Text.BodySmall>
<Text.CaptionDefault>Helper caption</Text.CaptionDefault>
<Text.CaptionSmall>Helper caption</Text.CaptionSmall>

The available names match the text styles you have configured in the Typography editor. Every name is a property on the Text component, fully typed, so your IDE will suggest them.

Tutorial: Type that scales on the Zaklad blog walks through this in depth.

Icons

Icons live in the /icons subpath. Import the whole set as Icon and reference each one by name. Only the icons you actually use are bundled.

tsx
import * as Icon from "@your-org/ui/icons";

// Render an icon
<Icon.ArrowRight />

// Choose a variant and size
<Icon.ChevronRight variant={Icon.VARIANT.Solid} size={Icon.SIZE.Large} />

// Pass one to a component slot
<Button.Primary leadingIcon={Icon.ChevronRight.Solid}>Continue</Button.Primary>

Each icon takes a variant (when it has more than one), a size (an Icon.SIZE name or a raw pixel number), and an optional title that makes it readable to screen readers.

tsx
// Smallest bundle: import only the icons you use
import { ArrowRight } from "@your-org/ui/icons";

// Data-driven names (e.g. from a CMS): use DynamicIcon
import { DynamicIcon } from "@your-org/ui/icons";
<DynamicIcon name={iconName} size={Icon.SIZE.Medium} />

DynamicIcon opts into the full icon set, so it does not tree-shake. Reach for it only when the name is not known ahead of time. Passing true to a component's icon prop still uses whichever default was configured for that slot in the Components editor.

Tutorial: One icon system on the Zaklad blog walks through this in depth.

Breakpoints

The provider drives tier-aware component sizing through five default viewport breakpoints:

TierViewport widthTypical target
XS< 768 pxMobile
S768 – 1032 pxLarge mobile / small tablet
M1033 – 1439 pxTablet / small desktop
L1440 – 1919 pxDesktop
XL>= 1920 pxLarge desktop / 4K

To override the defaults, pass a media prop to the provider:

tsx
import { ZakladProvider, type ZakladMediaConfig } from "@your-org/ui";

const media: ZakladMediaConfig = {
  xs: { maxWidth: 639 },
  s: { minWidth: 640, maxWidth: 1023 },
  m: { minWidth: 1024, maxWidth: 1279 },
  l: { minWidth: 1280, maxWidth: 1535 },
  xl: { minWidth: 1536 },
};

<ZakladProvider media={media}></ZakladProvider>
Tutorial: Responsive without breakpoints on the Zaklad blog walks through this in depth.

Cross-platform

The package is built platform-neutral from the ground up. Components are authored against a shared API with platform-specific implementations behind it: web components render HTML with CSS custom properties, and React Native components use StyleSheet values resolved from the same token set. One token vocabulary drives both, with no separate imports or conditional platform code in your app.

tsx
// One import, same component API — on web and React Native
import { Button } from "@your-org/ui";

Web is fully supported through the package's browser and neutral builds (React SPAs and SSR alike). React Native and Expo are now supported too: the package ships a dedicated native build and a react-native export condition, so Metro resolves the native bundle automatically. There is no per-platform import and no conditional code in your app.

On a React Native or Expo project, install the two native peers the package leaves to you (your app already has react-native):

bash
# Expo
npx expo install react-native-svg expo-font

# Bare React Native
npm install react-native-svg expo-font

Then wrap your app in the generated provider exactly as on web. On native the provider calls loadFonts() on mount and gates render on the result, so your fonts register through expo-font automatically (text falls back to system fonts until loading completes). The web bundle is untouched and behaves exactly as before.

tsx
import { AcmeProvider } from "@your-org/ui";

export default function App() {
  return (
    <AcmeProvider>
      {/* your screens */}
    </AcmeProvider>
  );
}

Exporting your tokens

The whole token set can be exported as DTCG JSON (the Design Tokens Community Group format, an open standard). Export the current live state at any time, or the exact frozen tokens of any published release.

There are two reasons it exists. The first is flexibility: if you already have your own build setup and would rather transform the raw tokens yourself, the data is right there in a portable, standard shape.

The second is the important one, and it is deliberate. It is a clean exit. If you ever want to take your system and run it entirely on your own infrastructure, your tokens are yours and they leave in an open format. The platform is built to help you create and maintain a design system, not to trap you in one. For most teams the generated package, the Figma library, and the live MCP server are a better path than wiring all of that up by hand, but if you want out, the door is open.

Find the export in the Publish modal (current live tokens) and on any release in the release history (that version's frozen tokens).
Tutorial: Bringing an existing system in on the Zaklad blog walks through this in depth.