Skip to content

Getting started

Alacris is the runtime. Alacris UI (@alacris/ui on npm) is a complete design system built with it: 68 custom elements, a three-tier token system, and a theme engine that re-skins the whole page with one stylesheet write. Material Design is the default. Your design system is the point.

The tags are real custom elements. They work in a static HTML file, inside React, Vue, Svelte, Angular, Rails, Django, and — later — Alacris-Go live views. There is no adapter package.

Terminal window
npm install @alacris/ui

That installs @alacris/core as well. Theme first, so the first paint is already themed, then use the tags:

import { applyTheme } from '@alacris/ui';
applyTheme({ seed: '#0b57d0' });
<ui-button>Hello</ui-button>
<ui-text-field label="Email" clearable></ui-text-field>
<ui-switch label="Dark mode"></ui-switch>

Importing @alacris/ui registers every component. For a smaller page, import only what you use — each component module is self-contained:

import { applyTheme } from '@alacris/ui/theme';
import '@alacris/ui/components/ui-button.js';
import '@alacris/ui/components/ui-text-field.js';
applyTheme({ seed: '#0b57d0' });

The published package is plain ESM. An import map lets you write the same bare specifiers a bundler would resolve:

<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<script type="importmap">
{
"imports": {
"@alacris/core": "https://cdn.jsdelivr.net/npm/@alacris/core@0.11.3/dist/alacris.js",
"@alacris/ui": "https://cdn.jsdelivr.net/npm/@alacris/ui@0.3.0/src/index.js"
}
}
</script>
<script type="module">
import { applyTheme } from '@alacris/ui';
applyTheme({ seed: '#0b57d0' });
</script>
</head>
<body>
<ui-button>Hello</ui-button>
</body>
</html>

The URLs pin @alacris/core@0.11.3 and @alacris/ui@0.3.0. Never mix two versions of @alacris/core on one page — two copies means two reactive graphs.

<script type="importmap">
{
"imports": {
"@alacris/core": "https://cdn.jsdelivr.net/npm/@alacris/core@0.11.3/dist/alacris.js",
"@alacris/ui": "https://cdn.jsdelivr.net/npm/@alacris/ui@0.3.0/src/index.js"
}
}
</script>

Register the elements once at the app entry, then render tags. Props are both attributes and properties, so React 19, Vue, Svelte, Angular, and server-rendered HTML all work without wrappers. The full recipes — Next, Nuxt, SvelteKit, Rails, Django, Laravel, Phoenix, HTMX — are on Using it from a framework.

<script type="module">
import { applyTheme } from '@alacris/ui';
applyTheme({ seed: '#0b57d0' });
</script>
<ui-button>Save</ui-button>
<ui-text-field name="email" label="Email"></ui-text-field>

Named controls are form-associated. Give ui-text-field, ui-switch, ui-checkbox, ui-select and the rest a name and they submit, reset, and follow <fieldset disabled> like native fields.

Every visual decision flows through CSS custom properties. Components never hardcode a colour, radius, or duration — they speak only sys.* tokens — so a theme change restyles everything without a re-render.

import { createTheme, applyTheme, setScheme, toggleScheme } from '@alacris/ui/theme';
applyTheme({ seed: '#6750a4' });
applyTheme({ seed: '#b3261e', density: -1 });
setScheme('dark');
toggleScheme();
applyTheme(createTheme({
colors: { primary: '#0b57d0', tertiary: '#00695c' },
typography: { brand: 'Inter, sans-serif', scale: 1.05 },
shape: { radius: 0.5 },
overrides: { light: { 'color-surface': '#faf7f2' } },
}));

applyTheme writes one document-level stylesheet of --ui-* custom properties and replaces it in place on the next call. Scheme switching after that is data-ui-scheme on <html> (light | dark | auto).

Three places to intervene, from broadest to narrowest:

  1. Re-theme the systemapplyTheme({ seed, shape, … }). Every component follows. Dark mode is generated automatically.
  2. Re-skin one component typeui-button { --ui-button-radius: 4px; }.
  3. Reach inside one instanceui-dialog::part(surface) { backdrop-filter: blur(8px) }.

The last two are the same consumer theming contract Alacris itself teaches (vars(), ::part). Alacris UI adds the first layer: a generated Material-shaped palette and a sys accessor so components never write raw var(--ui-…) strings.

Durations and easings are tokens, so the theme’s motion.scale governs CSS transitions and JS animation alike, and prefers-reduced-motion is honoured automatically:

import { html } from '@alacris/core';
import { animate, fx, presence } from '@alacris/ui/motion';
animate(el, fx.slideInUp, { duration: 'medium2', easing: 'emphasizedDecelerate' });
html`${presence(open, () => html`<div class="sheet">…</div>`, {
enter: fx.slideInUp, exit: fx.slideOutDown,
})}`;

presence is the exit-animation hook fine-grained rendering does not give you on its own.

Import What it is
@alacris/ui registers every component; re-exports theme, motion, tokens, utilities
@alacris/ui/theme createTheme, applyTheme, scheme switching
@alacris/ui/motion animate, fx, presence, withFlip, ripple
@alacris/ui/tokens sys and the token engines
@alacris/ui/components/ui-button.js one element, registered as a side effect

Component modules have side effects (they call define()). Theme, motion and token modules do not, so a bundler can tree-shake them.

The live catalog is the kitchen sink: every component plus a theme playground, composed from the same tags you import from npm. It is a real page on this site — link people there.

A small app that uses those tags lives in starter/ of the repo. Copy that folder as an install guide; it is not the published package. From a clone, npm run demo serves the catalog at http://localhost:5173/ui/ and the starter at http://localhost:5173/starter/.