DocumentationComponentsTheme CreatorGitHub
Theme CreatorGitHubIntroduction
InstallationUsageTheming
ComponentsAccordionAction SheetAlertAlertDialogArea ChartAspectRatioAvatarBadgeBannerBar ChartBottom NavBreadcrumbButtonButtonGroupCalendarCardCarouselChartChatBubbleChatBubbleNewCheckboxChipCoachMarkCodeBlockCollapsibleColor PickerComboboxCommandContainerContextMenuDate PickerDividerDrawerDropdown MenuEmptyStateFABFieldFieldsetFile UploadFlexFormGridHoverCardIconButtonLabelLine ChartLinkMenubarMeterModalNavigation MenuNumberFieldOTP InputPaginationPickerPie ChartPopoverProgressPromptInputRadar ChartRadial ChartRadioRatingScroll AreaSearchSegmentedControlSelectShortcutSidebarSkeletonSliderCircularSliderMediaTrimmerSpacerSpinnerSplit ButtonStackStatusStepperSurfaceSwitchTableTabsTextareaTime PickerToastToggleButtonToggleGroupTokenizerToolbarTooltipTop Header DesktopTop Header Mobile
Contributing
Components

MediaTrimmer

A trim range drawn over a filmstrip, with an optional playhead and ruler — plus MediaSeekBar, a scrub bar with a hover preview frame. Three separately named sliders, full keyboard operation, and min/max selection length.

Playground

Installation

npx shadcn@latest add https://tessinaui.com/r/media-trimmer.json

Usage

import { MediaTrimmer, MediaSeekBar } from "@tessinaui/ui";
<MediaTrimmer
  duration={30}
  value={range}
  onValueChange={setRange}
  defaultPlayhead={12}
  minDuration={1}
/>

When to use it

MediaTrimmer picks a range out of a clip — the filmstrip trim every video editor ships (Pinterest, Vimeo, TikTok, Whatnot in ledgers/slider_VARIANT_LEDGER.md, row 12). MediaSeekBar picks a point in one — the player scrub bar with a preview bubble (Netflix, Disney+, Tubi).

Neither is a Slider variant. The media rendering and the scrub mechanics are their own object: a slider has one value per thumb and no opinion about frames, while a trimmer has a selection, a playback position and a strip of images that all have to stay in step.

Examples

Default

A trim range with a playhead. Clicking the strip seeks; dragging a handle trims.

Ruler and constraints

A longer clip with timecode ticks, and a selection held between 10 s and 3 min. Both limits are enforced by moving the handle you are dragging — a trimmer that drags the far end along with your finger loses the edit you already made.

Sizes

Seek bar

A scrub bar with a buffered range and a preview bubble. previewAt returns the still for a position, or null when you have none — the bubble then shows the timecode alone rather than a broken image.

Accessibility

  • The trimmer is a role="group" containing three separately named sliders: trim start, trim end, and playback position. They answer different questions, and a single control with modes could not tell a screen-reader user which one was about to move.
  • Every handle announces a timecode, not raw seconds — aria-valuetext is 1:15, or 1:15.0 when the step is finer than a second.
  • The filmstrip and the ruler are aria-hidden. They are visual scales, and every value they show is already in a handle's aria-valuetext; announcing them would add "image, image, image" and nothing else.
  • Keyboard: arrows step, Shift+arrow and PageUp/PageDown take the large step, Home/End jump to the clip bounds.
  • Handles are ~12px wide because they have to sit on the cut, so the 44px touch target is an invisible centred pad on phones (WCAG 2.5.5).
  • Without frames the strip draws placeholder cells rather than collapsing, so the control does not change height when thumbnails decode and take the handles with it.

Time runs left-to-right at every dir. A filmstrip is a sequence of real frames in capture order, so mirroring the track under RTL would play the clip backwards to look correct. The surrounding labels and layout still follow the document direction.

API Reference

MediaTrimmer

PropTypeDefaultDescription
durationnumber—Required. Total length of the media, in seconds
value[number, number]—Controlled selection, in seconds
defaultValue[number, number]whole clipUncontrolled initial selection
onValueChange(value) => void—Fires on every change
onValueCommitted(value) => void—Fires once when a drag ends, or immediately for a keyboard step
minDurationnumberstepShortest selection the handles may produce
maxDurationnumber—Longest selection the handles may produce
stepnumber0.1Granularity, in seconds
largeStepnumberstep × 10Step for PageUp/PageDown and Shift+arrow
playheadnumber—Controlled playback position. Omit both playhead props to hide it
defaultPlayheadnumber—Uncontrolled initial position
onPlayheadChange(time) => void—Fires when the playhead moves
framesMediaFrame[]—Stills to draw behind the track
showRulerbooleanfalseTick marks and timecodes above the strip
showDurationbooleantrueRead-out of the selected length below the strip
size"sm" | "md" | "lg""md"Strip height — 44 / 56 / 72px
intent"none" | "error" | "warning" | "success" | "info""none"Handle and selection colour
formatTime(seconds: number) => stringtimecodeHuman text for the read-out and aria-valuetext
labelstring"Trim media"Group name
handleLabels[string, string]["Trim start", "Trim end"]Names for the two trim handles
playheadLabelstring"Playback position"Name for the playhead
disabledbooleanfalse—
readOnlybooleanfalseFocusable, not changeable

MediaSeekBar

PropTypeDefaultDescription
durationnumber—Required. Total length, in seconds
valuenumber—Controlled playback position
defaultValuenumber0Uncontrolled initial position
onValueChange(time) => void—Fires on every change
onValueCommitted(time) => void—Fires once when a scrub ends
bufferednumber—How far the media has buffered, drawn behind the played fill
stepnumber1Granularity, in seconds
largeStepnumberstep × 10Step for PageUp/PageDown and Shift+arrow
previewAt(seconds) => string | null—Still for a hovered or scrubbed position. null shows the timecode alone
showPreviewbooleantrueSuppress the preview bubble entirely
size"sm" | "md" | "lg""md"Track thickness
intentMediaTrimmerIntent"none"Fill colour
formatTime(seconds: number) => stringtimecodeHuman text
labelstring"Seek"Accessible name
disabledbooleanfalse—

MediaFrame

FieldTypeDescription
srcstringImage URL
timenumberWhere in the media the frame came from
altstringUnused by the strip, which is decorative — kept for consumers reusing the type

formatTimecode

formatTimecode(seconds, precise?) — 0:09, 1:15, 1:01:01. With precise, keeps one decimal under ten minutes.

MediaTrimmerSkeleton · MediaSeekBarSkeleton

Placeholders whose boxes come from the components' own height maps, so the two cannot drift.

CircularSliderSpacer

On this page

PlaygroundInstallationUsageWhen to use itExamplesDefaultRuler and constraintsSizesSeek barAccessibilityAPI ReferenceMediaTrimmerMediaSeekBarMediaFrameformatTimecodeMediaTrimmerSkeleton · MediaSeekBarSkeleton