---
name: loke-ui
type: core
library: "@loke/ui"
library_version: "1.0.0-rc.1"
description: >
  Entry point and router for @loke/ui headless React UI primitives. Subpath imports only —
  never import from @loke/ui directly. 19 components across overlays (Dialog, AlertDialog,
  Popover, Tooltip, DropdownMenu), forms (Checkbox, RadioGroup, Select, Switch, Label),
  navigation (Accordion, Tabs, Collapsible, Command), and composition (Primitive+Slot,
  Context+Collection, Overlay Infrastructure, Hooks). All are thin shells over 4 shared
  infrastructure patterns: DismissableLayer, FocusScope, Presence, useControllableState.
---

# @loke/ui

Headless, unstyled React UI primitives with granular subpath exports. Every component ships accessible behavior, keyboard navigation, and ARIA semantics. Styling is entirely your responsibility.

## Import Rule

**ALWAYS** import from the component subpath. Never from the root package or Radix paths.

```tsx
// CORRECT
import { Dialog, DialogContent, DialogTitle } from "@loke/ui/dialog";
import { Select, SelectItem, SelectItemText } from "@loke/ui/select";

// WRONG — these all fail
import { Dialog } from "@loke/ui";
import { Dialog } from "@radix-ui/react-dialog";
import { Dialog } from "@loke/design-system";
```

## Mental Model

All 19 components are thin shells over 4 shared infrastructure patterns. Learn these once and every component becomes readable:

| Infrastructure | What it does | Components that use it |
|---|---|---|
| `DismissableLayer` | Stacked dismiss logic, outside-click, escape key | Dialog, Popover, Tooltip, DropdownMenu |
| `FocusScope` | Focus trapping, looping, auto-focus on open | Dialog, AlertDialog, Popover (modal) |
| `Presence` | Animation state machine — delays unmount for exit transitions | All overlays with `forceMount` |
| `useControllableState` | Controlled/uncontrolled dual-mode with dev warnings | All stateful components |

## Sub-Skills

Route to the skill that matches what you're building:

| Need to… | Skill |
|---|---|
| Build a modal or non-modal dialog | [`dialog`](../dialog/SKILL.md) |
| Build a destructive confirmation dialog | [`alert-dialog`](../alert-dialog/SKILL.md) |
| Build a floating panel anchored to a trigger | [`popover`](../popover/SKILL.md) |
| Add hover/focus tooltips | [`tooltip`](../tooltip/SKILL.md) |
| Build a trigger-based dropdown menu | [`dropdown-menu`](../dropdown-menu/SKILL.md) |
| Build accessible checkboxes (incl. indeterminate) | [`checkbox`](../checkbox/SKILL.md) |
| Build a single-selection radio group | [`radio-group`](../radio-group/SKILL.md) |
| Build a listbox-style select for forms | [`select`](../select/SKILL.md) |
| Build an on/off toggle switch | [`switch`](../switch/SKILL.md) |
| Label a form control accessibly | [`label`](../label/SKILL.md) |
| Build expandable section groups | [`accordion`](../accordion/SKILL.md) |
| Build a tabbed interface | [`tabs`](../tabs/SKILL.md) |
| Build a single collapsible section | [`collapsible`](../collapsible/SKILL.md) |
| Build a command palette or searchable list | [`command`](../command/SKILL.md) |
| Use `asChild` / delegate element rendering | [`primitive-and-slot`](../primitive-and-slot/SKILL.md) |
| Build compound components with scoped context | [`context-and-collection`](../context-and-collection/SKILL.md) |
| Build a custom overlay from scratch | [`overlay-infrastructure`](../overlay-infrastructure/SKILL.md) |
| Use shared hooks (controllable state, size, etc.) | [`hooks`](../hooks/SKILL.md) |
| Unsure which component to use | [`choosing-the-right-component`](../choosing-the-right-component/SKILL.md) |

## Quick Decision Tree

```
Need floating/overlay UI?
├─ Triggered by hover/focus only + non-interactive content → Tooltip
├─ Triggered by click + interactive content (forms, buttons) → Popover
├─ Triggered by click + list of actions → DropdownMenu
├─ Triggered by click + form value selection (static list) → Select
├─ Triggered by click + searchable/async option list → Popover + Command
├─ Full overlay with title + description
│  ├─ Destructive action confirmation → AlertDialog
│  └─ General purpose modal/sheet → Dialog
└─ Nothing floats, just expand/collapse
   ├─ Single section → Collapsible
   ├─ Multiple coordinated sections → Accordion
   └─ Tabbed panel switching → Tabs

Need a form control?
├─ Boolean on/off toggle → Switch
├─ Multi-select or tri-state (indeterminate) → Checkbox
├─ Single selection from a group → RadioGroup
├─ Pick a value, submit in a form → Select
└─ Any control needs an accessible name → Label
```

## All Subpath Imports

```tsx
import { ... } from "@loke/ui/accordion";
import { ... } from "@loke/ui/alert-dialog";
import { ... } from "@loke/ui/checkbox";
import { ... } from "@loke/ui/collapsible";
import { ... } from "@loke/ui/command";
import { ... } from "@loke/ui/context";
import { ... } from "@loke/ui/dialog";
import { ... } from "@loke/ui/dismissable-layer";
import { ... } from "@loke/ui/dropdown-menu";
import { ... } from "@loke/ui/focus-guards";
import { ... } from "@loke/ui/focus-scope";
import { ... } from "@loke/ui/label";
import { ... } from "@loke/ui/popper";
import { ... } from "@loke/ui/popover";
import { ... } from "@loke/ui/portal";
import { ... } from "@loke/ui/presence";
import { ... } from "@loke/ui/primitive";
import { ... } from "@loke/ui/radio-group";
import { ... } from "@loke/ui/select";
import { ... } from "@loke/ui/slot";
import { ... } from "@loke/ui/switch";
import { ... } from "@loke/ui/tabs";
import { ... } from "@loke/ui/tooltip";
import { ... } from "@loke/ui/use-callback-ref";
import { ... } from "@loke/ui/use-controllable-state";
import { ... } from "@loke/ui/use-direction";
import { ... } from "@loke/ui/use-escape-keydown";
import { ... } from "@loke/ui/use-id";
import { ... } from "@loke/ui/use-is-document-hidden";
import { ... } from "@loke/ui/use-is-hydrated";
import { ... } from "@loke/ui/use-layout-effect";
import { ... } from "@loke/ui/use-previous";
import { ... } from "@loke/ui/use-size";
```

## Key Patterns

**All overlay components share `forceMount` for exit animations:**
```tsx
// Without forceMount, the element unmounts before the exit animation runs
<DialogPortal forceMount>
  <DialogOverlay forceMount className="animate-fade-out data-[state=open]:animate-fade-in" />
  <DialogContent forceMount className="animate-slide-out data-[state=open]:animate-slide-in">
    ...
  </DialogContent>
</DialogPortal>
```

**All stateful components support controlled and uncontrolled mode:**
```tsx
// Uncontrolled (internal state)
<Dialog defaultOpen={false}>...</Dialog>

// Controlled (external state)
<Dialog open={open} onOpenChange={setOpen}>...</Dialog>
```

**Do not mix controlled and uncontrolled.** Switching at runtime triggers dev warnings and loses state.
