Vaul
An unstyled, physics-based drawer component for React, built on Radix UI's Dialog primitive.
Repository Health
Technical Analysis
Vaul is a React component library for building drawers — bottom sheets, side panels, and swipeable overlays — modeled after the drawer interaction popularized by iOS. It wraps Radix UI’s Dialog primitive with pointer-based drag tracking, velocity-aware snap points, and background scaling so the resulting drawer feels physically responsive rather than purely CSS-animated.
The library ships as a small set of composable components (Root, Trigger, Content, Overlay, Handle, NestedRoot, Portal) that mirror Radix’s own API shape, and it is unstyled by default, leaving all visual styling to the consuming application while vaul handles the gesture physics, focus management, and scroll-locking behavior.
What You Get
- Drawer.Root/Trigger/Content/Overlay/Handle/NestedRoot components with a Radix-like compound API
- Pointer-based drag tracking with velocity thresholds, dampening, and directional (top/bottom/left/right) support
- Configurable snap points (percentage or pixel) with fade-from-index background overlay control
- Built-in scroll locking, iOS Safari viewport/keyboard handling, and background scaling on open
- Nested drawer support via Drawer.NestedRoot for stacked sheet flows
Common Use Cases
- Mobile-style bottom sheets for menus, filters, or action lists in a responsive web app
- Side-panel drawers for cart, settings, or detail views triggered by a click
- Multi-step snap-point sheets, e.g. a half-open preview that expands to full height
- shadcn/ui-style drawer components layered on top of Radix + Tailwind design systems
Under The Hood
Architecture — Vaul is a single-package React component library (~2,300 lines across 12 TS/TSX files under src/) that wraps Radix UI’s Dialog primitive (DialogPrimitive.Root/Content/Overlay/Trigger/Portal/Close/Title/Description) with pointer-event-based drag physics. The core Root component (src/index.tsx) owns all interaction state (isDragging, activeSnapPoint, drawer/overlay refs) via React state and refs, and exposes it through a DrawerContext (src/context.ts) that Content/Overlay/Handle subcomponents consume via useDrawerContext(). Dragging is handled with native pointer events rather than a gesture library, translating the drawer’s DOM node directly via CSS transforms (helpers.ts: set/getTranslate/dampenValue) for smooth movement, then handing off to Radix’s own open/close state machine on release. Snap points are computed as a separate concern in use-snap-points.ts, converting percentage/pixel snap definitions into pixel offsets relative to the window or a custom container, and deciding on release whether to snap to the nearest point or fling based on velocity thresholds. Two hooks handle platform quirks: use-position-fixed.ts works around iOS Safari’s viewport/scroll bugs by pinning body position while a modal drawer is open, and use-prevent-scroll.ts (adapted from Adobe’s react-spectrum) blocks background touch-scrolling on iOS during drag.
Tech Stack — TypeScript (96% of the codebase) targeting React 16.8 through 19 as peer dependencies, with a single runtime dependency: @radix-ui/react-dialog (^1.1.1), which supplies accessible dialog semantics (focus trap, ARIA, escape-to-close) that Vaul layers drag/snap behavior on top of. The package is built with bunchee into dual ESM/CJS output with generated .d.ts files, and the monorepo uses pnpm workspaces + Turborepo to coordinate the library package and a separate test/ Next.js app used for Playwright e2e tests. Styling ships as a small style.css file for defaults rather than a CSS-in-JS runtime, keeping the library unopinionated about the consumer’s styling approach.
Code Quality — There is a real, if end-to-end rather than unit, test suite: test/tests/*.spec.ts uses Playwright against a dedicated Next.js app covering base behavior, controlled mode, nested drawers, snap points, scaled/unscaled backgrounds, non-dismissible drawers, and handle interactions — a meaningful signal given how gesture-heavy this code is. Source files are consistently typed with explicit prop interfaces (DialogProps, HandleProps, ContentProps) and use React.forwardRef correctly, though several internal call sites use @ts-ignore and any casts around vendor-prefixed CSS properties and a WeakMap-cached style object in helpers.ts. Naming is consistent and descriptive, but the core Root component in index.tsx is a single ~700-line function with many closures and refs, concentrating most of the library’s complexity in one file. Error handling is minimal by design, with most edge cases guarded by early-return checks and one explicit thrown Error in NestedRoot for genuine misuse (nesting outside a parent drawer).
API Design — The public API is a small, Radix-shaped compound-component object (Drawer.Root, .Trigger, .Content, .Overlay, .Handle, .NestedRoot, .Portal, .Close, .Title, .Description) that developers already familiar with Radix Dialog or shadcn/ui can pick up with near-zero new concepts — Trigger/Close/Title/Description are literally re-exported from @radix-ui/react-dialog. A basic drawer needs only a trigger and content wrapped in Root, with sensible defaults (modal=true, dismissible=true, direction=‘bottom’) covering the common case, while advanced behavior (snap points, custom container, handle-only dragging, fade-from-index) is opt-in via JSDoc comments directly on each prop that double as inline editor documentation. The main weakness is that project-level documentation lives almost entirely outside the repo, on the official docs site, since the README is now reduced to a single unmaintained-status notice — so newcomers exploring the repo itself have to read prop JSDoc and the Playwright tests to understand behavior.
Used by 47 apps in this directory
Activepieces
Automation · AI Assistants
Open-source AI automation platform that converts 280+ workflow integrations into MCP servers for LLMs, with no-code builders and TypeScript extensibility.
AFFiNE
Productivity · Project Management · Note Taking
Write, draw, and plan in one infinite canvas — the open-source alternative to Notion and Miro that keeps your data yours.
Amical
Note Taking · AI Assistants
Local-first AI dictation that understands your active app — private, offline, and built for speed.
AutoGPT
Automation · Productivity · AI Assistants
Build, deploy, and run autonomous AI agents that automate complex multi-step workflows using a visual block-based graph editor.
BaseBuddy
CMS
A self-hosted content editor for existing Postgres or Supabase schemas — maps the tables you already have into a WordPress-like TipTap editor instead of requiring you to reshape your database around a CMS.
Cherry Studio
AI Assistants
All-in-one AI desktop client with 300+ assistants and multi-model support
Cline
AI Code Assistants
An open-source AI coding agent that lives in your editor and terminal — reads and edits your codebase, runs commands, browses the web, and requires human approval for every action by default.
Dub
Marketing · Analytics
The open-source link attribution platform for short links, conversion tracking, and affiliate programs — powering 100M+ clicks monthly.
Dyad
AI Development · Productivity · AI Code Assistants
Build AI-powered apps locally with your own keys, your own models, and zero cloud lock-in.