Getting Started
ARC UI is 169 Lit web components that run natively in the browser, with typed wrappers generated for seven frameworks from the same source. One install, one import, and you are rendering.
Install
npm install @arclux/arc-ui lit
pnpm add @arclux/arc-ui lit
yarn add @arclux/arc-ui lit
<link rel="stylesheet"
href="https://unpkg.com/@arclux/arc-ui@4.9.1/base.css">
<script type="module"
src="https://unpkg.com/@arclux/arc-ui@4.9.1/register"></script>
<arc-button variant="primary">Click Me</arc-button>
One runtime dependency: Lit. The CDN tab needs no build step at all.
Your First Component
- Import the tokens and register the components
The stylesheet carries every design token; the register module defines all169 custom elements.
jsimport '@arclux/arc-ui/base.css'; import '@arclux/arc-ui/register'; - Load the fonts
ARC UI ships no font files.
base.cssnames three reference faces (Host Grotesk for text, Tomorrow for labels, JetBrains Mono for code) and loads none of them, so without this step everything quietly renders insystem-ui. A font CDN is the quickest way in:html<link rel="preconnect" href="https://fonts.googleapis.com"> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Host+Grotesk:wght@300..800&family=JetBrains+Mono:wght@400..700&family=Tomorrow:wght@400;500;600&display=swap">To self-host, or to use your own typefaces instead, seeLoading the Font Files.
- Write the markup
They are HTML elements. No provider, no wrapper, no client directive.
html<arc-button variant="primary">Get Started</arc-button> <arc-input label="Email" placeholder="you@example.com"></arc-input> <arc-toggle label="Dark mode"></arc-toggle> - That is it. This is that markup, live
Rendered by this page from the snippet above, not a screenshot of it.
Get Started
Import Patterns
Three ways in, and the one you want depends on whether you are shipping a page or an app. Start at the top and move down when bundle size starts to matter.
@arclux/arc-ui/registerEvery component. Simplest, and the right default while you are exploring.@arclux/arc-ui/buttonOne component and its dependencies. Tree-shakeable, and what a production app should use.{ ArcButton } from '@arclux/arc-ui'The named class, for extending or instantiating in script. Pulls in the full registry.arc-code-block is the one exception: it carries a syntax highlighter, so it
is excluded from every barrel. Import it at @arclux/arc-ui/code-block.
Framework Setup
Prism reads the Lit source and generates a native wrapper package per framework: typed props, idiomatic events, tree-shakeable imports. Same components, different import:
import { Button, Card } from '@arclux/arc-ui-react'; // React
import { Button, Card } from '@arclux/arc-ui-vue'; // Vue
import { Button, Card } from '@arclux/arc-ui-svelte'; // Svelte
import { Button, Card } from '@arclux/arc-ui-angular'; // Angular
import { Button, Card } from '@arclux/arc-ui-solid'; // Solid
import { Button, Card } from '@arclux/arc-ui-preact'; // Preact
The Frameworks guide covers install and usage for each one, including the SSR story.
Theming
Three theme modes, one attribute on <html>:
<html data-theme="dark"> <!-- default -->
<html data-theme="light">
<html data-theme="auto"> <!-- follows OS preference -->
The toggle in this site's top bar sets exactly that attribute. Try it. For a theme of your own, override the tokens (see Theming) or build one visually in the Theme Synthesizer and export it.
Design Tokens
237 custom properties drive the visual language, and they are the same ones the components read. Import the stylesheet and your own CSS speaks the same system:
@import '@arclux/arc-ui/base.css';
.my-component {
color: var(--text-primary);
background: var(--bg-card);
padding: var(--space-md);
border-radius: var(--radius-md);
font-family: var(--font-body);
box-shadow: var(--shadow-md);
}
--space-xs · sm · md · lg · xl · 2xl--radius-xs · sm · md · lg · xl · full--shadow-xs · sm · md · lg · xlThe Design Tokens reference lists every one.
TypeScript
The framework wrappers ship their own types. For plain web components, add the ambient declarations and every arc-* element gets autocomplete and checking in JSX and template literals:
{
"compilerOptions": {
"types": ["@arclux/arc-ui/types"]
}
}
Your Own Components
A component you write with Lit has its own shadow root, and a shadow root does not see document styles. The *, *::before, *::after { box-sizing: border-box }reset in your global stylesheet stops at its boundary, so padded boxes inside it overflow in a way nothing on the page explains.
Adopt the styles ARC UI's own components start from.tokenStyles carries the box-sizing and margin reset, the static token defaults, and the reduced-motion rule:
import { LitElement, html, css } from 'lit';
import { tokenStyles } from '@arclux/arc-ui/shared-styles';
class AppPanel extends LitElement {
static styles = [
tokenStyles,
css`
:host { display: block; padding: var(--space-md); }
`,
];
render() {
return html`<slot></slot>`;
}
}
customElements.define('app-panel', AppPanel);
Themed tokens (colors, shadows, gradients) are not in it and do not need to be. Custom properties inherit through the shadow boundary, so they arrive from base.csson the document as they do for every ARC component.
tokenStyles also zeroes every margin and padding, as ARC's components expect. To add only the box-sizing reset to components you already have, adoptresetStyles from the same module instead.
Testing
Some components read the children you write as data and render their own copies. Thearc-menu-items inside arc-dropdown-menu and thearc-options inside arc-segmented-control andarc-select are display: none. The visible, clickable item is the copy in the parent's shadow root. A test that clicks the element you authored fails with "element is not visible", however correct the markup is. Each component that works this way says so on its default slot ("read as data") in the API reference and incustom-elements.json, where each parent also lists the elements it expects under children, with their slot.
Find the rendered item the way a user does, by its role and its text. The rendered copies carry the roles (menuitem, radio, option), and Playwright's role locators pierce shadow roots:
// Playwright: the rendered copy, found by its role and text
await page.getByRole('menuitem', { name: 'Rename' }).click();
await page.getByRole('radio', { name: 'Weekly' }).click();
// Not the element you authored: it is display: none, and never receives the click
// await page.locator('arc-menu-item[value="rename"]').click();
Going to Production
Nothing here is needed to build with ARC UI. It covers what to do once you are shipping it.
A custom element renders its fallback content until JavaScript registers it.base.css ships the guard for that (:not(:defined) { opacity: 0 }), so unregistered components stay hidden and fade in as their definitions land. On a page registering many of them, two additions make the upgrade invisible.
Register what is above the fold first. Static imports of the components visible on first paint form a small, fast chunk; the full registry loads lazily behind it.
import '@arclux/arc-ui/base.css';
// Shell components visible on first paint — small synchronous chunk
import '@arclux/arc-ui/top-bar';
import '@arclux/arc-ui/button';
import '@arclux/arc-ui/badge';
// Everything else — lazy chunk that fades in when ready
import('@arclux/arc-ui/register');
Reserve space for layout-critical elements. A hidden element still occupies layout, so give shell components their final size before they upgrade and nothing shifts when the definitions arrive.
arc-top-bar:not(:defined) {
display: block;
height: 64px;
}
arc-button:not(:defined) {
display: inline-flex;
min-height: 40px;
}
This site does exactly that: the visible frame upgrades from a chunk roughly an eighth the size of the full registry. If you server-render, seeServer Rendering: every component emits declarative shadow DOM, so the markup arrives styled before any of this matters.