{"_id":"react-video-kit","_rev":"2-80d49be80943fad0e07374a171ecfb3c","name":"react-video-kit","dist-tags":{"latest":"3.0.0"},"versions":{"2.0.0":{"name":"react-video-kit","version":"2.0.0","keywords":["react","video","player","ios","component","ui","ui-component","ui-kit","ui-library","react-video-kit","react-video","react-ios-video","video component","video kit"],"author":{"name":"Mohamed Achaq","email":"hi@achaq.dev"},"license":"Apache-2.0","_id":"react-video-kit@2.0.0","maintainers":[{"name":"chaqchase","email":"hi@chaqchase.com"}],"homepage":"https://video-kit.chaqchase.com","bugs":{"url":"https://github.com/chaqchase/video-kit/issues"},"dist":{"shasum":"cac871c4bcac6b1d5b9e96060f27469c4e24b722","tarball":"https://registry.npmjs.org/react-video-kit/-/react-video-kit-2.0.0.tgz","fileCount":8,"integrity":"sha512-0CaVs+jNfH23ORL8OjS86hcO6eRgBkSPyLV22ZEOAeOEuXrQ20K8Z/8iwPIAxHr9v82aJCba6Abq8ooGK/tZdw==","signatures":[{"sig":"MEUCIBJ9MEltssQ6VNZflNpUMNQrJaamxrkj8y8r5LRSYR2qAiEA6ir2HBCXD0WVh78m6d4PlxBMIl8m46VG1fGpzEUaUZo=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":168696},"main":"./dist/index.js","_from":"file:react-video-kit-2.0.0.tgz","types":"./dist/index.d.ts","module":"./dist/index.mjs","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.mjs","module":"./dist/index.mjs","require":"./dist/index.js"}},"scripts":{"dev":"tsup --watch","build":"tsup","check-types":"tsc --noEmit"},"_npmUser":{"name":"chaqchase","email":"hi@chaqchase.com"},"_resolved":"/tmp/4caa689c49e5013ba283c01638c2cf2c/react-video-kit-2.0.0.tgz","_integrity":"sha512-0CaVs+jNfH23ORL8OjS86hcO6eRgBkSPyLV22ZEOAeOEuXrQ20K8Z/8iwPIAxHr9v82aJCba6Abq8ooGK/tZdw==","repository":{"url":"git+https://github.com/chaqchase/video-kit.git","type":"git"},"_npmVersion":"10.8.2","description":"A video component for react inspired by the ios video player","directories":{},"_nodeVersion":"18.20.8","dependencies":{"clsx":"^2.0.0","react":"18.2.0","react-dom":"18.2.0","typescript":"4.9.5","tailwind-merge":"^2.1.0","@radix-ui/react-slider":"^1.1.2","@radix-ui/react-dropdown-menu":"^2.0.6"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"tsup":"^8.0.1","react":"^18.2.0","eslint":"^7.32.0","postcss":"^8.4.32","typescript":"^4.9.4","tailwindcss":"^3.3.6","@types/react":"^18.2.45","autoprefixer":"^10.4.16","framer-motion":"^10.16.16","@types/react-dom":"18.2.7","@repo/typescript-config":"0.0.0"},"peerDependencies":{"react":">=18","react-dom":">=18","framer-motion":">=10.0.0"},"_npmOperationalInternal":{"tmp":"tmp/react-video-kit_2.0.0_1761577180439_0.9143851301637691","host":"s3://npm-registry-packages-npm-production"}},"3.0.0":{"name":"react-video-kit","version":"3.0.0","description":"A video component for react inspired by the ios video player","license":"Apache-2.0","main":"./dist/index.js","module":"./dist/index.mjs","types":"./dist/index.d.ts","sideEffects":["**/*.css"],"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.mjs","module":"./dist/index.mjs","require":"./dist/index.js"}},"peerDependencies":{"motion":">=10.0.0","react":">=18","react-dom":">=18"},"dependencies":{"@radix-ui/react-slider":"^1.1.2","@radix-ui/react-slot":"^1.1.0","clsx":"^2.0.0","tailwind-merge":"^2.1.0"},"publishConfig":{"access":"public"},"devDependencies":{"@types/react":"^18.2.45","@types/react-dom":"18.2.7","autoprefixer":"^10.4.16","concurrently":"^8.2.2","eslint":"^7.32.0","motion":"^12.23.24","postcss":"^8.4.32","react":"^18.2.0","react-dom":"^18.2.0","tailwindcss":"^3.3.6","tsup":"^8.0.1","typescript":"^4.9.4","@repo/typescript-config":"0.0.0"},"author":{"name":"Mohamed Achaq","email":"hi@achaq.dev"},"homepage":"https://react-vide-kit.chaqchase.com","repository":{"type":"git","url":"git+https://github.com/chaqchase/video-kit.git"},"bugs":{"url":"https://github.com/chaqchase/video-kit/issues"},"keywords":["react","video","player","ios","component","ui","ui-component","ui-kit","ui-library","react-video-kit","react-video","react-ios-video","video component","video kit"],"scripts":{"build":"pnpm build:css && tsup","build:css":"tailwindcss -i ./src/index.css -o ./src/styles.css --minify","dev":"pnpm build:css && pnpm dev:watch","dev:watch":"concurrently \"tailwindcss -i ./src/index.css -o ./src/styles.css --watch\" \"tsup --watch\"","check-types":"tsc --noEmit"},"_id":"react-video-kit@3.0.0","_integrity":"sha512-IrzcfJRMJ4+dUECDKMkM8yZ0TKVyYKFtucH6PUv2SeQajijfk9SQfT5eVcEMCq+Kixm+q3Oqx0jJw+f33trohw==","_resolved":"/tmp/0e9225f3420bf4b561837254e79a496b/react-video-kit-3.0.0.tgz","_from":"file:react-video-kit-3.0.0.tgz","_nodeVersion":"18.20.8","_npmVersion":"10.8.2","dist":{"integrity":"sha512-IrzcfJRMJ4+dUECDKMkM8yZ0TKVyYKFtucH6PUv2SeQajijfk9SQfT5eVcEMCq+Kixm+q3Oqx0jJw+f33trohw==","shasum":"d80d7b740e76df430c03008676ba10f124f0717b","tarball":"https://registry.npmjs.org/react-video-kit/-/react-video-kit-3.0.0.tgz","fileCount":8,"unpackedSize":238242,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIAmZIO1my9kXpn/rzWR136JWDZqrzBXBXL/7YhvfKvUTAiEA5UYZUB7gqoVf8VIG9tJo9WRzxpCv7P7epfwTba0MaWE="}]},"_npmUser":{"name":"chaqchase","email":"hi@chaqchase.com"},"directories":{},"maintainers":[{"name":"chaqchase","email":"hi@chaqchase.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/react-video-kit_3.0.0_1761598239051_0.3600498615204071"},"_hasShrinkwrap":false}},"time":{"created":"2025-10-27T14:59:40.369Z","modified":"2025-10-27T20:50:39.427Z","2.0.0":"2025-10-27T14:59:40.641Z","3.0.0":"2025-10-27T20:50:39.247Z"},"bugs":{"url":"https://github.com/chaqchase/video-kit/issues"},"author":{"name":"Mohamed Achaq","email":"hi@achaq.dev"},"license":"Apache-2.0","homepage":"https://react-vide-kit.chaqchase.com","keywords":["react","video","player","ios","component","ui","ui-component","ui-kit","ui-library","react-video-kit","react-video","react-ios-video","video component","video kit"],"repository":{"type":"git","url":"git+https://github.com/chaqchase/video-kit.git"},"description":"A video component for react inspired by the ios video player","maintainers":[{"name":"chaqchase","email":"hi@chaqchase.com"}],"readme":"# react-video-kit\n\nA composable video player for React with a shadcn/ui-style API. Built for flexibility, accessibility, and SSR compatibility.\n\n## Features\n\n- **Composable primitives** for building custom video UIs\n- **Declarative sources and tracks** (no DOM manipulation)\n- **Imperative ref API** for programmatic control\n- **Accessible** with ARIA labels and keyboard support\n- **SSR-safe** for Next.js and other frameworks\n- **Fully typed** with TypeScript\n- **Isolated styling** with Tailwind CSS (rv- prefix)\n\n## Installation\n\n```bash\nbun add react-video-kit\n# or\npnpm add react-video-kit\n# or\nnpm install react-video-kit\n# or\nyarn add react-video-kit\n```\n\n## Styling and Tailwind\n\n- The library ships precompiled CSS with an `rv-` prefix for all utilities and\n  components. No global resets are injected.\n- Works seamlessly in apps using Tailwind v3 or v4 — and also in apps without\n  Tailwind at all.\n- Because styles are precompiled and prefixed, there are no class name or\n  preflight conflicts with your app’s styling.\n\nNotes:\n\n- If your application relied on Tailwind’s preflight/base from this library,\n  include your own base styles in the app instead.\n- You do not need to process this library through your Tailwind build.\n\n## Quick Start\n\n```tsx\nimport { Video } from \"react-video-kit\";\n\nexport default function App() {\n  return (\n    <Video.Root src=\"https://example.com/video.mp4\">\n      <Video.Media />\n      <Video.Center>\n        <Video.PlayPause />\n      </Video.Center>\n    </Video.Root>\n  );\n}\n```\n\n## API Overview\n\n### Layout Components\n\n- **`Video.Root`** - Provider component that manages state and context\n- **`Video.Media`** - Renders the HTML video element with sources and tracks\n- **`Video.Backdrop`** - Gradient overlay backdrop\n- **`Video.Header`** - Top control bar container\n- **`Video.Center`** - Center control area container\n- **`Video.Footer`** - Bottom control bar container\n\n### Typography\n\n- **`Video.Title`** - Video title display\n- **`Video.Subtitle`** - Video subtitle display\n\n### Controls\n\n- **`Video.PlayPause`** - Toggle play/pause button\n- **`Video.SeekBack`** - Seek backward button (customizable seconds)\n- **`Video.SeekForward`** - Seek forward button (customizable seconds)\n- **`Video.FullscreenToggle`** - Fullscreen mode toggle\n- **`Video.PipToggle`** - Picture-in-picture toggle\n- **`Video.CaptionsMenu`** - Dropdown menu for selecting captions/subtitles\n- **`Video.QualityMenu`** - Dropdown menu for switching video quality\n- **`Video.Volume.Button`** - Mute/unmute button\n- **`Video.Volume.Slider`** - Volume level slider\n- **`Video.Timeline`** - Video progress slider\n- **`Video.Time.Current`** - Current time display\n- **`Video.Time.Remaining`** - Remaining time display\n- **`Video.Loading`** - Loading indicator\n\n### Menus and Fullscreen\n\n- Menus automatically portal into the fullscreen element when the player is\n  fullscreen, and portal to `document.body` otherwise. This prevents clipping\n  from parent overflow and ensures menus open correctly in both modes.\n\n## Examples\n\n### Full-Featured Player\n\n```tsx\nimport { Video } from \"react-video-kit\";\n\nexport default function Player() {\n  return (\n    <Video.Root\n      src=\"https://example.com/video.mp4\"\n      title=\"My Video\"\n      subtitle=\"Video description\"\n      autoPlay\n    >\n      <Video.Media />\n      <Video.Backdrop />\n\n      <Video.Header>\n        <div className=\"rv-w-full rv-flex\">\n          <Video.FullscreenToggle />\n          <Video.PipToggle />\n        </div>\n        <div className=\"rv-w-full rv-flex rv-justify-end rv-items-center rv-h-fit\">\n          <Video.Volume.Button />\n          <Video.Volume.Slider />\n        </div>\n      </Video.Header>\n\n      <Video.Center>\n        <Video.SeekBack seconds={10} />\n        <Video.PlayPause />\n        <Video.SeekForward seconds={10} />\n        <Video.Loading />\n      </Video.Center>\n\n      <Video.Footer>\n        <div className=\"rv-flex rv-flex-col\">\n          <Video.Subtitle />\n          <Video.Title />\n        </div>\n        <Video.Timeline />\n        <div className=\"rv-flex rv-justify-between rv-w-full\">\n          <Video.Time.Current />\n          <Video.Time.Remaining negative />\n        </div>\n      </Video.Footer>\n    </Video.Root>\n  );\n}\n```\n\n### Multiple Sources & Quality Switching\n\nProvide multiple video sources for different quality levels. The `QualityMenu` component allows users to switch between quality levels while preserving currentTime and play state:\n\n```tsx\n<Video.Root\n  src={[\n    { src: \"/video-1080p.mp4\", type: \"video/mp4\", label: \"HD\" },\n    { src: \"/video-720p.mp4\", type: \"video/mp4\", label: \"SD\" },\n  ]}\n  defaultQualityIndex={0}\n  onQualityChange={(index, source) => {\n    console.log(\"Quality changed to:\", source.label);\n  }}\n>\n  <Video.Media />\n  <Video.Footer>\n    <Video.QualityMenu />\n  </Video.Footer>\n</Video.Root>\n```\n\nWhen switching quality:\n\n- Current playback time is preserved\n- Play state is maintained (if playing, resumes after switch)\n- Minimal buffering/stutter during transition\n\n### Text Tracks (Captions/Subtitles)\n\nAdd captions or subtitles using the tracks prop:\n\n```tsx\n<Video.Root\n  src=\"https://example.com/video.mp4\"\n  tracks={[\n    {\n      src: \"/captions-en.vtt\",\n      kind: \"captions\",\n      srclang: \"en\",\n      label: \"English\",\n      default: true,\n    },\n    {\n      src: \"/captions-es.vtt\",\n      kind: \"captions\",\n      srclang: \"es\",\n      label: \"Spanish\",\n    },\n  ]}\n>\n  <Video.Media />\n  <Video.Footer>\n    <Video.CaptionsMenu />\n  </Video.Footer>\n</Video.Root>\n```\n\nThe `CaptionsMenu` component automatically lists all available tracks plus an \"Off\" option. You can also provide callbacks to track changes:\n\n```tsx\n<Video.Root\n  src=\"https://example.com/video.mp4\"\n  tracks={tracks}\n  defaultTrackIndex={0}\n  onTrackChange={(index, track) => {\n    console.log(\"Active track:\", index, track);\n  }}\n>\n  <Video.Media />\n  <Video.Footer>\n    <Video.CaptionsMenu />\n  </Video.Footer>\n</Video.Root>\n```\n\n### Custom Styling\n\nAll components accept className props for custom styling:\n\n```tsx\n<Video.Root className=\"max-w-4xl mx-auto\">\n  <Video.Media className=\"rounded-2xl\" />\n  <Video.Header className=\"bg-gradient-to-b from-black/70\">\n    <Video.FullscreenToggle className=\"text-blue-500\" />\n  </Video.Header>\n  <Video.Timeline\n    className=\"my-timeline\"\n    trackClassName=\"bg-gray-700\"\n    rangeClassName=\"bg-blue-500\"\n    thumbClassName=\"bg-blue-600\"\n  />\n</Video.Root>\n```\n\n### Custom Elements with `asChild`\n\nButton-based controls support the `asChild` prop, allowing you to use your own custom elements while preserving the control's behavior:\n\n```tsx\n<Video.Root src=\"https://example.com/video.mp4\">\n  <Video.Media />\n  <Video.Center>\n    {/* Use a custom button element */}\n    <Video.PlayPause asChild>\n      <button className=\"my-custom-play-button\">\n        {isPlaying ? \"⏸\" : \"▶\"}\n      </button>\n    </Video.PlayPause>\n  </Video.Center>\n  <Video.Footer>\n    {/* Custom fullscreen button */}\n    <Video.FullscreenToggle asChild>\n      <div className=\"custom-fullscreen-toggle\">Toggle FS</div>\n    </Video.FullscreenToggle>\n  </Video.Footer>\n</Video.Root>\n```\n\nComponents with `asChild` support:\n\n- `Video.PlayPause`\n- `Video.SeekBack` / `Video.SeekForward`\n- `Video.FullscreenToggle`\n- `Video.PipToggle`\n- `Video.Volume.Button`\n\n### Imperative Control\n\nAccess video controls programmatically using a ref:\n\n```tsx\nimport { useRef } from \"react\";\nimport { Video, VideoHandle } from \"react-video-kit\";\n\nexport default function ControlledPlayer() {\n  const videoRef = useRef<VideoHandle>(null);\n\n  return (\n    <div>\n      <Video.Root ref={videoRef} src=\"https://example.com/video.mp4\">\n        <Video.Media />\n        <Video.Center>\n          <Video.PlayPause />\n        </Video.Center>\n      </Video.Root>\n\n      <div className=\"controls\">\n        <button onClick={() => videoRef.current?.play()}>Play</button>\n        <button onClick={() => videoRef.current?.pause()}>Pause</button>\n        <button onClick={() => videoRef.current?.seek(30)}>Skip to 30s</button>\n        <button onClick={() => videoRef.current?.setVolume(0.5)}>\n          50% Volume\n        </button>\n        <button onClick={() => videoRef.current?.mute()}>Mute</button>\n        <button onClick={() => videoRef.current?.enterFullscreen()}>\n          Fullscreen\n        </button>\n      </div>\n    </div>\n  );\n}\n```\n\n### VideoHandle Methods\n\n- `play()` - Start or resume playback\n- `pause()` - Pause playback\n- `seek(seconds)` - Seek to specific time\n- `setVolume(value)` - Set volume (0-1)\n- `mute()` - Mute audio\n- `unmute()` - Unmute audio\n- `enterFullscreen()` - Enter fullscreen mode\n- `exitFullscreen()` - Exit fullscreen mode\n- `enterPip()` - Enter picture-in-picture\n- `exitPip()` - Exit picture-in-picture\n\n## Props Reference\n\n### VideoRootProps\n\n| Prop                  | Type                                                  | Description                              |\n| --------------------- | ----------------------------------------------------- | ---------------------------------------- |\n| `src`                 | `VideoSource`                                         | Video URL or array of source objects     |\n| `tracks`              | `VideoTrack[]`                                        | Text tracks for captions/subtitles       |\n| `poster`              | `string`                                              | Poster image URL                         |\n| `title`               | `string`                                              | Video title                              |\n| `subtitle`            | `string`                                              | Video subtitle                           |\n| `autoPlay`            | `boolean`                                             | Whether to autoplay (default: true)      |\n| `loop`                | `boolean`                                             | Whether to loop playback                 |\n| `showControls`        | `boolean`                                             | Whether to show controls (default: true) |\n| `className`           | `string`                                              | Additional CSS classes                   |\n| `defaultTrackIndex`   | `number \\| null`                                      | Initial active track index (null = Off)  |\n| `defaultQualityIndex` | `number`                                              | Initial quality index when src is array  |\n| `onProgress`          | `(time: number) => void`                              | Called on progress update                |\n| `onDuration`          | `(duration: number) => void`                          | Called when duration available           |\n| `onEnded`             | `() => void`                                          | Called when playback ends                |\n| `onPlay`              | `() => void`                                          | Called when playback starts              |\n| `onPause`             | `() => void`                                          | Called when paused                       |\n| `onLoad`              | `() => void`                                          | Called when loaded                       |\n| `onVolumeChange`      | `(volume: number) => void`                            | Called on volume change                  |\n| `onTrackChange`       | `(index: number \\| null, track?: VideoTrack) => void` | Called when active caption track changes |\n| `onQualityChange`     | `(index: number, source: {src, type, label}) => void` | Called when video quality/source changes |\n\n### VideoSource Type\n\n```typescript\ntype VideoSource = string | Array<{ src: string; type: string; label: string }>;\n```\n\n### VideoTrack Type\n\n```typescript\ntype VideoTrack = {\n  src: string;\n  kind: TextTrackKind;\n  srclang?: string;\n  label?: string;\n  default?: boolean;\n};\n```\n\n## Accessibility\n\nAll controls include proper ARIA labels and keyboard support:\n\n- **Space** - Play/pause\n- **Arrow Left/Right** - Seek backward/forward 5s\n- **Arrow Up/Down** - Volume up/down\n- **F** - Toggle fullscreen\n- **M** - Toggle mute\n- **P** - Toggle picture-in-picture\n- **Escape** - Exit fullscreen/PiP\n\n### Screen Reader Support\n\n- All interactive controls have descriptive `aria-label` attributes\n- Sliders (timeline, volume) include `aria-valuetext` for meaningful value announcements\n- Menu items use `role=\"menuitemradio\"` and `aria-checked` for selection state\n- Focus management follows WAI-ARIA best practices\n\n### Keyboard Navigation\n\nHotkey scope can be configured per player:\n\n```tsx\n<Video.Root src=\"video.mp4\" hotkeys={{ scope: \"focused\", enabled: true }}>\n  {/* ... */}\n</Video.Root>\n```\n\nScope options:\n\n- `\"focused\"` (default) - Hotkeys only work when player is focused\n- `\"hovered\"` - Hotkeys work when mouse is over player\n- `\"global\"` - Hotkeys work anywhere on the page\n\n### Reduced Motion\n\nThe player respects the `prefers-reduced-motion` media query for animations. All motion effects are automatically disabled when users have this preference enabled.\n\n## SSR Compatibility\n\nThe component is fully SSR-compatible with Next.js and other frameworks. All DOM operations are guarded within effects and client-side checks.\n\n```tsx\n// Works in Next.js App Router\nimport { Video } from \"react-video-kit\";\n\nexport default function Page() {\n  return (\n    <Video.Root src=\"/video.mp4\">\n      <Video.Media />\n      <Video.Center>\n        <Video.PlayPause />\n      </Video.Center>\n    </Video.Root>\n  );\n}\n```\n\n## Browser Support\n\nSupports all modern browsers with native HTML5 video support.\n\n### Cross-Browser Compatibility\n\n- **Chrome/Edge** - Full support for all features\n- **Firefox** - Full support for all features\n- **Safari** - Full support with webkit-specific fullscreen API fallbacks\n- **Mobile browsers** - Supports native video controls fallback on iOS Safari\n\n### Safari-Specific Behavior\n\n**Text Tracks:**\n\n- Caption tracks are managed via the `textTracks` API with `mode` toggling\n- The \"Off\" option properly disables all tracks by setting `mode: \"disabled\"`\n- `defaultTrackIndex` applies once on mount; subsequent changes are user-controlled\n\n**Quality Switching:**\n\n- Quality changes preserve `currentTime` and play state\n- Playback rate is maintained across quality switches\n- Error handling reverts to previous working source on load failure\n\n### Known Limitations\n\n- Picture-in-picture may not be supported on all mobile devices\n- Fullscreen API behavior varies slightly between browsers (handled automatically)\n- Some mobile browsers override video controls behavior\n\n## License\n\nApache-2.0\n","readmeFilename":"README.md"}