The web renderer
The DOM branch of AsheeUI: install, the Tailwind 4 stylesheet, the provider, routing substitution, the web-only names and SSR.
@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,
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
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
/* 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:
@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:
@source "../../packages/kit/src/**/*.tsx";
The provider
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:
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:
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:
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 has the full contract.
Testing
The web suite runs on the DOM:
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 states the current set.