Duck Volume
The tap: a mute toggle with the water hidden behind it, opening sideways when the pointer or the focus ring arrives.
InputsClient componentlucide-react
Preview
Installation
The CLI writes the source into your project and pulls in whatever it depends on.
$
pnpm dlx shadcn@latest add @duck/duck-volumePulls in @duck/theme, @duck/duck-media-slider, @duck/quack-button. Already installed items are skipped.
Usage
import { DuckVolume } from "@/components/ui/duck-volume"The file lands in components/ui/duck-volume.tsx and belongs to you from that point on. Edit it in place rather than wrapping it.
Props
Everything not listed here is forwarded to the underlying element.
| Prop | Type | Default | Description |
|---|---|---|---|
| volume / defaultVolume | number | 0.7 | 0 to 1, matching video.volume. Controlled or uncontrolled. |
| muted / defaultMuted | boolean | false | Matching video.muted, and independent of volume on purpose — either one alone makes silence. |
| onVolumeChange | (volume: number) => void | - | Fires live as the slider moves. Volume has no timeupdate to fight, so there is nothing to defer. |
| onMutedChange | (muted: boolean) => void | - | Fires from the toggle, and from dragging the level back up out of silence. |
| collapsible | boolean | true | Off, the slider stays open. For a settings panel, where nothing is tight. |
Rules
What keeps this component from turning into noise.
- Silence is muted || volume === 0. Read one of the two and you get a speaker icon over a silent film.
- Unmuting a slider that sits at 0 restores the last audible level, otherwise the icon changes and nothing else does.
- Dragging to 0 does not flip video.muted. It is already silent, and inventing that state confuses the element you are driving.
- Collapsed is zero width, never hidden: the slider stays in the tab order and focus-within is what opens it.
