hiraki

開き

Japanese · noun · "an opening" or "the act of opening"

A zero-dependency React drawer component. All 4 directions, velocity-aware gestures, snap points, 6 variants, and a pure CSS animation system without Radix, Framer Motion, or any external runtime dependencies.

Behavior-first primitives. No CSS file, no classNames. Style with Tailwind, CSS variables, or the style prop. Keep the behavior, redesign the surface.

DocsGitHub
install.sh
pnpm add hiraki

All 4 directions

bottom · top · left · right. Each direction correctly handles gesture axis, scroll detection, and transforms.

Velocity-aware

Inertia-based snap point targeting. Fast release overshoots, slow release lands precisely. Exponential smoothing.

Snap points

Pixels, percentages, or "content". Controlled and uncontrolled. Multiple simultaneous snap positions.

Zero dependencies

React is the only peer dep. No Radix, no Framer Motion, nothing else. ~10 KB gzipped.

Accessible

ARIA dialog, focus trap, Escape to dismiss, scroll lock, iOS Safari fix, screen reader announcements.

CSS animations

Pure CSS transitions applied imperatively. Slide, spring, scale, morph presets. Respects prefers-reduced-motion.

usage

Built for DX

Compound component API: compose Root, Content, Overlay and add what you need. Nothing more.

basic.tsx
import { Drawer } from 'hiraki'

function App() {
  const [open, setOpen] = useState(false)

  return (
    <Drawer.Root open={open} onOpenChange={setOpen}>
      <Drawer.Trigger asChild>
        <button>Open</button>
      </Drawer.Trigger>
      <Drawer.Portal>
        <Drawer.Overlay />
        <Drawer.Content>
          <Drawer.Handle />
          <Drawer.Title>Hello</Drawer.Title>
          <Drawer.Description>
            A zero-dependency drawer.
          </Drawer.Description>
          <Drawer.Close asChild>
            <button>Close</button>
          </Drawer.Close>
        </Drawer.Content>
      </Drawer.Portal>
    </Drawer.Root>
  )
}
snap-points.tsx
<Drawer.Root
  snapPoints={['25%', '50%', '90%']}
  activeSnapPoint={activeSnap}
  onSnapPointChange={setActiveSnap}
>
  <Drawer.Portal>
    <Drawer.Overlay />
    <Drawer.Content>
      <Drawer.Handle />
      <Drawer.SnapIndicator />
      {/* content */}
    </Drawer.Content>
  </Drawer.Portal>
</Drawer.Root>
direction.tsx
{/* Opens from the right */}
<Drawer.Root direction="right">
  <Drawer.Portal>
    <Drawer.Overlay />
    <Drawer.Content>
      <Drawer.Handle />
      <nav>Sidebar navigation</nav>
    </Drawer.Content>
  </Drawer.Portal>
</Drawer.Root>

styling

Same primitive, different product voice

Hiraki keeps the behavior layer stable and lets your team push the visual direction. The homepage only shows one stronger example here so the rest of the landing can stay minimal.

accessiblebehavior-firstbring your own stylesredesignable

Style B: Editorial side panel

Bold, branded, and sharp. Same API, completely different visual language.

component example

A louder editorial treatment, built with the same `Drawer.Root`, `Drawer.Overlay`, `Drawer.Content`, `Drawer.Title`, and `Drawer.Close` primitives.

editorial-panel.tsx
import { Drawer } from 'hiraki'

export function EditorialPanel() {
  return (
    <Drawer.Root direction="right">
      <button className="border border-orange-400 bg-orange-500 px-4 py-2 text-sm font-black uppercase tracking-[0.18em] text-black">
        Open editorial panel
      </button>
      <Drawer.Portal>
        <Drawer.Overlay className="bg-black/70" />
        <Drawer.Content className="h-full max-w-md border-l-4 border-orange-500 bg-neutral-950 text-orange-50">
          <div className="border-b border-orange-500/30 px-6 py-5">
            <Drawer.Title className="text-xs font-black uppercase tracking-[0.3em] text-orange-400">
              Issue 04
            </Drawer.Title>
            <Drawer.Description className="mt-3 text-2xl font-semibold leading-tight text-orange-50">
              Same primitive, completely different visual language.
            </Drawer.Description>
          </div>
        </Drawer.Content>
      </Drawer.Portal>
    </Drawer.Root>
  )
}

variants

6 visual modes, one API

direction

All 4 directions

snapPoints

Snap points

Pixels, percentages, or "content" with velocity-aware behavior. Flick fast to jump between snaps. Drag slowly to land precisely.

get started

Open source, free forever

MIT licensed. No paid tiers, no telemetry, no vendor lock-in. Contributions welcome.

npm.sh
npm install hiraki
pnpm.sh
pnpm add hiraki

hiraki (開き), React drawer component · MIT License