`@asheeui/web` is the renderer a browser compiles. It owns every class string, every DOM element and
every browser behaviour: a `Button` is a real `<button>` or `<a>`, a `Table` is a `<table>`, a `Modal` is
a dialog with focus containment. The contract is the same one the native renderer implements; the
implementation is the browser's.

Most applications do not import it directly. A shared screen imports [`asheeui`](/platforms/umbrella),
which resolves to this package on the web. Install it directly when the application is web-only or when
the file is already browser-specific.

## Install

```bash
npm install asheeui @asheeui/web
# or, for a web-only application:
npm install @asheeui/web
```

`@asheeui/web` depends on `@asheeui/core`, `@floating-ui/react`, `clsx` and `tailwind-merge`. React and
React DOM are peers.

## The stylesheet

```css
/* src/styles.css */
@import "tailwindcss";
@import "asheeui/styles";
```

`asheeui/styles` is the umbrella's one-line entry: it imports the renderer's tokens. A direct install can
name the renderer's file instead:

```css
@import "tailwindcss";
@import "@asheeui/web/styles";
```

The order matters. The framework's file extends the Tailwind theme with the colour tokens
(`--color-primary` and the rest), so it must come after Tailwind's own import. A monorepo adds a
`@source` for the shared package, because Tailwind 4 scans from the CSS file:

```css
@source "../../packages/kit/src/**/*.tsx";
```

## The provider

```tsx
import { AsheeUIProvider, type ExternalConfig } from "@asheeui/web";
import type { ReactNode } from "react";

const config: ExternalConfig = {
  color: {
    light: { primary: "#2563EB", background: "#FFFFFF" },
    dark: { primary: "#60A5FA", background: "#0B1220" },
  },
  defaultTheme: "system",
  defaultRadius: "md",
};

export function Root({ children }: { children: ReactNode }) {
  return <AsheeUIProvider config={config}>{children}</AsheeUIProvider>;
}
```

The provider writes the `--ashee-*` custom properties the classes read, so the palette is one object
rather than a stylesheet per theme. It also resolves the configuration cascade: instance prop, component
section, application default, documented value.

## Routing

The framework does not import a router. A control that navigates is an `<a>`, and a framework that wants
client-side navigation substitutes its own component through `link`:

```tsx
import { Link } from "@tanstack/react-router";
import { Button, Navbar } from "asheeui";

function RouterLink({ href, children, ...rest }: { href: string; children?: React.ReactNode }) {
  return (
    <Link to={href} {...rest}>
      {children}
    </Link>
  );
}

<Button
  link={{ component: RouterLink, props: {} }}
  href="/invoices"
>
  All invoices
</Button>;

<Navbar
  brand="Pulse"
  links={[
    { label: "Today", href: "/" },
    { label: "Workouts", href: "/workouts" },
  ]}
  link={{ component: RouterLink, props: {} }}
/>;
```

The adapter receives the `href` the control would have used, so the rest of the props stay the
component's. `Image` has the same substitution for a framework's image component (`next/image` and the
like).

## Web-only names

A handful of names exist only on this renderer because the concept is the browser's:

| Name | Why web-only |
| --- | --- |
| `Breadcrumb` | A URL trail has no device counterpart; native shows a titled header |
| `ResizableScreen` | A draggable split is a desktop idiom |
| `SkipLink` | A skip link compensates for the browser's tab order |
| `KeyboardProvider`, `OnScreenKeyboard`, the `useKeyboard*` hooks | A rendered keyboard compensates for a browser constraint |

`SkipLink` is on `@asheeui/web/section-kit`; the rest are on the package root. A shared file that imports
one is a compile error on a device, which is the correct result.

## SSR and the first paint

The provider belongs above the router's root, so the first render already has the configuration. The
provider applies the theme class and the colour-scheme to the document once it mounts; on a
server-rendered page, give the document the stored theme's class in the head so the first paint is
already themed. `THEME_STORAGE_KEY` is the key the controller reads, so the same value can be stated
in a small inline script:

```tsx
import { THEME_STORAGE_KEY } from "asheeui";

// Read the stored selection in the document head, before React runs, and put its class on <html>.
// The provider then finds the document already in the right theme.
```

Without a head script, a page renders in the default theme and switches once React hydrates. With it,
the document has the stored theme before the first paint.

## The Puck editor

The editor is a web concern. `@asheeui/puck` exports the same block specs for both platforms, and the web
half adds the editor:

```tsx
import { Puck } from "@puckeditor/core";
import { asheePuckConfig } from "@asheeui/puck";
import "@puckeditor/core/puck.css";

<Puck config={asheePuckConfig} data={data} onPublish={setData} />;
```

The native half imports nothing from `@puckeditor/core`, so a device bundle never reaches the editor.
[Puck](/platforms/puck) has the full contract.

## Testing

The web suite runs on the DOM:

```bash
pnpm --filter @asheeui/web test
```

Vitest and Testing Library are the tools the package uses. For an application, the same pair works:
render the provider and the screen, query by role, and assert the element the browser would expose. A
test that asserts a class name is testing the stylesheet, not the component.

## Version

`@asheeui/web` 2.8.0 is built against `@asheeui/core` 3.0.0. The two version independently; the
[release table](/releases/latest) states the current set.
