Doc Page
Quick Start (Vanilla)
Vanilla setup path with explicit DOM bindings, lifecycle cleanup, and platform constraints.
Pick An Adapter
Choose the adapter that matches the host runtime. All adapters share the same behavioral contract, but each one exposes lifecycle and state in the way that feels natural for that framework.
| Adapter | Install path | Best fit |
|---|---|---|
| React | @nake-ui/adapter-react |
JSX compound components and SSR/hydration checks |
| Vanilla | @nake-ui/adapter-vanilla |
Plain DOM, progressive enhancement, custom frameworks |
| Vue | @nake-ui/adapter-vue |
Vue 3 composition and SFC templates |
| Solid | @nake-ui/adapter-solid |
Signals and fine-grained JSX rendering |
| Svelte | @nake-ui/adapter-svelte |
Svelte compound primitive namespaces |
Use one adapter per rendered primitive instance. Avoid mixing framework wrappers around the same DOM nodes unless you own the lifecycle boundary and cleanup order.
Sources: shared/en/quick-start
Install
pnpm add @nake-ui/primitives @nake-ui/adapter-vanillaThe vanilla adapter has no framework peer dependency. It binds real DOM nodes and returns a lifecycle handle.
Sources: shared/en/quick-start, vanilla/en/quick-start
Render A First Primitive
Write stable HTML first:
<div data-demo="tabs">
<div data-slot="list" aria-label="Project sections"></div>
<button data-demo-tab="overview" type="button">Overview</button>
<button data-demo-tab="activity" type="button">Activity</button>
<section id="panel-overview">Release readiness.</section>
<section id="panel-activity">Recent activity.</section>
</div>Then bind it:
import { bindTabs } from "@nake-ui/adapter-vanilla";
const list = document.querySelector<HTMLElement>("[data-slot='list']");
const overview = document.querySelector<HTMLElement>("[data-demo-tab='overview']");
const activity = document.querySelector<HTMLElement>("[data-demo-tab='activity']");
const overviewPanel = document.querySelector<HTMLElement>("#panel-overview");
const activityPanel = document.querySelector<HTMLElement>("#panel-activity");
if (list && overview && activity && overviewPanel && activityPanel) {
const binding = bindTabs(
{
list,
tabs: { overview, activity },
panels: { overview: overviewPanel, activity: activityPanel }
},
[{ id: "overview" }, { id: "activity" }],
{ id: "project-tabs", defaultValue: "overview", activationMode: "manual" }
);
window.addEventListener("beforeunload", () => binding.destroy(), { once: true });
}Sources: shared/en/quick-start, vanilla/en/quick-start
Verify The Contract
After binding:
- The list has
data-ui="tabs",role="tablist", anddata-orientation. - Each tab has
role="tab",aria-selected,aria-controls, and rovingtabIndex. - Each panel has
role="tabpanel",aria-labelledby,data-state, andhiddenwhen inactive. destroy()can run more than once without leaving duplicate listeners.
Sources: shared/en/quick-start, vanilla/en/quick-start
Styling
Optional starter skin:
pnpm add @nake-ui/theme-vanillaimport "@nake-ui/theme-vanilla/styles.css";You can also write plain CSS against public attributes. The vanilla adapter does not own layout or visual classes.
Sources: shared/en/quick-start, vanilla/en/quick-start
Agent Checklist
Ask agents to generate both:
- Markup with stable selectors and IDs.
- Binding code with
bind*, registrations,sync()when needed, anddestroy()cleanup.
The cleanup path is part of the vanilla contract.
Sources: shared/en/quick-start, vanilla/en/quick-start
Platform Boundaries
Vanilla is the best adapter for progressive enhancement and custom framework integration. The host must own mount, update, and unmount timing. Floating primitives measure on the client and need valid anchor/content nodes before positioning sync.
Sources: shared/en/quick-start, vanilla/en/quick-start
Next Reading
- Installation (Vanilla)
- Adapter Integration (Vanilla)
- Primitive Concepts
- Components Overview
- Troubleshooting
Sources: vanilla/en/quick-start