shadimage
limeplay
uicaptionssource: ok · hit

Captions

npx shadcn add "https://limeplay.winoffrg.dev/r/captions.json"
free

No selection tags yet — empty means no evidence, not lack of support.

Dependencies

Bundle weight · light
ui-primitives@radix-ui/react-compose-refs@radix-ui/react-slot

Registry dependencies

https://limeplay.winoffrg.dev/r/media-provider.jsonhttps://limeplay.winoffrg.dev/r/use-captions.json

Files1 · source cached 10/14/2026, 1:36:04 PM

registry/default/ui/captions.tsx
"use client"

import { composeRefs } from "@radix-ui/react-compose-refs"
import { Slot } from "@radix-ui/react-slot"
import React, { useEffect } from "react"

import { Button } from "@/components/ui/button"
import { cn } from "@/lib/utils"
import {
  useCaptions,
  useCaptionsStore,
} from "@/registry/default/hooks/use-captions"
import { usePlayerStore } from "@/registry/default/hooks/use-player"

export interface CaptionsControlProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
  /**
   * Render as child component using Radix Slot
   * @default false
   */
  asChild?: boolean
  render?: React.ReactElement
  /**
   * Keyboard shortcut hint displayed in aria-label
   * @example "C"
   */
  shortcut?: string
}

export type CaptionsControlPropsDocs = Pick<
  CaptionsControlProps,
  "asChild" | "render" | "shortcut"
>

export const CaptionsControl = React.forwardRef<
  HTMLButtonElement,
  CaptionsControlProps
>((props, forwardedRef) => {
  const textTracks = useCaptionsStore((state) => state.tracks)
  const { toggleVisibility } = useCaptions()

  const {
    "aria-label": ariaLabelProp,
    asChild = false,
    children,
    disabled: userDisabled,
    onClick,
    render,
    shortcut,
    ...restProps
  } = props

  const Comp = render ? Slot : asChild ? Slot : Button

  const handleClick = (event: React.MouseEvent<HTMLButtonElement>) => {
    onClick?.(event)
    if (!event.defaultPrevented) {
      toggleVisibility()
    }
  }

  const isDisabled = !textTracks || textTracks.length === 0 || userDisabled

  const getDefaultAriaLabel = () => {
    const shortcutText = shortcut ? ` (keyboard shortcut ${shortcut})` : ""
    return `Captions${shortcutText}`
  }

  return (
    <Comp
      aria-keyshortcuts={shortcut}
      aria-label={ariaLabelProp ?? getDefaultAriaLabel()}
      data-label="lp-captions-control"
      disabled={isDisabled}
      {...restProps}
      onClick={handleClick}
      ref={forwardedRef}
    >
      {render ? React.cloneElement(render, undefined, children) : children}
    </Comp>
  )
})

CaptionsControl.displayName = "CaptionsControl"

export type CaptionsContainerPropsDocs = Pick<
  CaptionsContainerProps,
  "fontScale"
>

interface CaptionsContainerProps extends React.ComponentPropsWithoutRef<"div"> {
  /**
   * Font scale factor for caption text size
   * @default 1
   */
  fontScale?: number
}

export const CaptionsContainer = React.forwardRef<
  HTMLDivElement,
  CaptionsContainerProps
>((props, ref) => {
  const { className, fontScale, ...etc } = props
  const player = usePlayerStore((state) => state.instance)
  const setContainerElement = useCaptionsStore(
    (state) => state.setContainerElement
  )

  useEffect(() => {
    if (player && fontScale) {
      player.configure({
        textDisplayer: {
          fontScaleFactor: fontScale,
        },
      })
    }
  }, [player, fontScale])

  return (
    <div
      className={cn(
        "relative flex w-full grow flex-col justify-end text-lg",
        className
      )}
      ref={composeRefs(ref, setContainerElement)}
      {...etc}
    />
  )
})

CaptionsContainer.displayName = "CaptionsContainer"

More from limeplay

Browse all components in this library →