Skip to main content
DOCS

React and React Native package documentation

Installing and using the generated npm package, switching themes, and font delivery.

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 organization: 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.

The component pack

Beyond the original component set, the library includes a further pack of components, grouped into two areas. Core components are general-purpose interface pieces: headers, empty states, form wrappers, tables, and more. AI components are built for chat and agent interfaces: chat messages, replies that stream in, and displays for a running tool call. Together with the original set, the full library totals 58 components.

How pack components reach a project

The component picker lists all 58 components for every project, new or existing. The picker reads one shared list with no per-project scope, so there is no adopt step, no publisher account, and no opt-in. A project you started years ago shows the same 58 choices as a project you start today.

A newly created project starts with all 58 components already added. The Setup Wizard's starter template pre-adds them, so a new project has the full set in place with no extra step.

An existing project is not changed by this. Nothing is added to it automatically, and no token is created on its behalf. An existing project keeps only the components it already had, until you add a pack component yourself from the picker, exactly the way you add any other component today.

A pack component can need a token or a composed component your project does not have. When that happens, adding it is rejected and the platform names exactly what is missing. The platform never creates the missing token or component for you.

Core components

Core components are general-purpose interface pieces. Use them in any project, the same way you use the original built-in components.

ComponentWhat it is
Form field wrapperA label, an input, and helper or error text, grouped as one field.
Empty stateAn icon, a title, a description, and one action, shown when there is no content to display.
Section headerA title, an optional description, and an optional action, marking the start of one section on a page.
LinkText styled and bound to a destination, for navigating rather than triggering an action.
CalloutA short note with an icon, a title, and an explanation, set apart from surrounding content.
Notification itemOne row in a list of notifications or tasks, with an image, a title, and a meta line.
Page headerThe title at the top of a page, with an optional description, an action row, and an optional summary panel.
Top nav barThe navigation strip that stays at the top of the page, with a brand image and slots for links and an action.
Code blockA fixed block of monospaced code or preformatted text.
PopoverA trigger that opens a small panel anchored beside itself.
Action sheetA trigger that opens a list of actions from the bottom of the screen.
Bottom sheetA trigger that opens a content surface from the bottom of the screen.
Progress overlayA centered modal that reports the progress of one operation.
ImageOne image at a fixed aspect ratio, with its own loading and error states.
Search barA query field with a clear action, for filtering or searching a list as you type.
OTP inputA single field for entering a one-time passcode or PIN.
NumberInputA numeric field with buttons to step the value up or down, within a minimum and a maximum.
List itemA vertical list of rows, each with a thumbnail image and a label.
Avatar groupA row of overlapping avatars, one per person.
StepperA multi-step indicator with previous and next controls.
PaginationA strip of page-number controls with previous and next controls.
ComboboxA query field that filters a list of options as you type, with one selected value.
DataTableA sortable, multi-select grid of rows and columns, with keyboard navigation across the rows.
Date pickerA calendar grid for one month at a time, with previous and next month controls.
Stat tileOne named metric shown with an animated numeric value.

Known limits

  • Bottom sheet: has no drag-to-dismiss. It opens and closes on the trigger action only. There is no swipe gesture to dismiss it.
  • Code block: has no syntax highlighting and no copy-to-clipboard action. Its text carries one text style and one color for the whole block, so coloring individual words or tokens is not something the current building blocks can express.
  • Responsive switching: no component switches its own layout automatically for a smaller screen. DataTable's card layout for narrow screens is a variant you choose yourself when you use the component. It never switches on its own based on screen size or platform.
  • DataTable: does not reuse the built-in Table component. Table hides its rows behind one fixed shape, and DataTable's sorting, multi-select, and keyboard navigation each need to address one row directly, so DataTable is built from smaller pieces instead.
  • Popover, Action sheet, Bottom sheet, Progress overlay, and Combobox, on a native mobile app: these components have no true overlay layer, no focus trap, and no scroll lock outside the browser. The developer package intentionally does not depend on any native-only overlay library.

AI components

AI components are built for chat and agent interfaces: showing a message, a reply that streams in, or the state of a running action.

ComponentWhat it is
Chat messageOne message in a conversation, with a sender image, a name, and the message text.
Thinking indicatorA pulsing icon that shows an AI or a process is working on a reply.
Streaming textA block of text that types itself in as it arrives, such as an AI reply.
Approval cardA request for a yes-or-no decision, with a pending, submitting, failed, and resolved state.
Tool use displayOne named tool call and its progress, from queued through running to a result or a failure.
Prompt barA growing message field with a send action, for composing a message to an AI assistant.

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.