UIdocs/ui/getting-started.md

Getting started with @arachnejs/ui

@arachnejs/ui is a component kit for Arachne: 330+ components built on @arachnejs/render (JSX) and @arachnejs/signals. They are accessible by default, they server-render, and you can restyle them without forking.

Guides: getting started · customization · theming · accessibility · SSR & hydration · component reference

Install #

bun add @arachnejs/ui @arachnejs/render @arachnejs/signals
bun add -d @arachnejs/vite

The package ships TypeScript/JSX source. Your bundler compiles it together with your app through the Arachne JSX plugin, which handles .tsx files in node_modules too:

// build.ts (Bun)
import { bunPlugin } from "@arachnejs/vite";

await Bun.build({ entrypoints: ["./src/client.tsx"], outdir: "dist", plugins: [bunPlugin()] });
// vite.config.ts
import { vitePlugin } from "@arachnejs/vite";

export default { plugins: [vitePlugin()] };

TypeScript needs the Arachne JSX runtime:

{ "compilerOptions": { "jsx": "react-jsx", "jsxImportSource": "@arachnejs/render" } }

Stylesheet #

Import the stylesheet once, from your entry module or your HTML:

import "@arachnejs/ui/styles.css";

All kit rules live in @layer arachne.tokens, arachne.components, so your own (unlayered) CSS always wins without specificity fights. See customization.

First component #

import { render } from "@arachnejs/render";
import { signal } from "@arachnejs/signals";
import { Button, FormField, Stack, TextInput, Title } from "@arachnejs/ui";

function Profile() {
    const name = signal("");
    return (
        <Stack gap="1rem">
            <Title order={1}>Profile</Title>
            <FormField label="Display name" labelFor="name" help="Shown on your public page.">
                <TextInput id="name" value={name()} onInput={(e) => name.set((e.target as HTMLInputElement).value)} />
            </FormField>
            <Button onClick={() => console.info("saved", name())}>Save</Button>
        </Stack>
    );
}

render(() => <Profile />, document.getElementById("app")!);

Components take plain values; read signals in JSX (value={name()}) and the compiler keeps the binding live. Event delegation is set up automatically by compiled modules, so you don't call delegateEvents yourself.

Next steps #

  • Browse the component reference. Every entry has an example, its slots and its props, generated from the source.
  • Run the playground (bun run --cwd apps/playground start) for live previews of every component.
  • Read about theming (palettes, dark mode, tokens) and customization (props, slots, configureUI).