Mixer
betaBalancing several signals against each other. Two arrangements: a console that shows every channel at once, and a studio that shows one in depth.
Also called Console, Channel Strip, Fader Bank, Level Mixer — in this system all of them are Mixer.
Everything below is the real component. Change the controls, tab through it, and turn on Inspector Mode to read any value off the screen.
One channel strip
The unit that repeats. Number and colour for identity, a waveform to recognise the clip, the gain readout as text, pan, mute and solo, then the fader and its meter side by side.
Solo dims what it silenced
Channel 4 is soloed, so channels 1, 2 and 3 are inaudible without any of their own controls having moved. Dimming them is what stops the user concluding the mixer is broken.
The studio arrangement
A track list on the left, and everything to the right is the selected track. This is what you build once a track needs more than a fader — volume, pan, presence, an enhance pass and a noise gate will not fit five abreast. The trade is real: you can work in depth, and you can no longer see the balance.
Mix Studio
Tracks
- Voice
- Music
- Effects
- Ambience
- Clips
Voice controls
The everyday four: how loud, where in the stereo field, how present, and whether it is heard at all.
Auto mix
Auto mix ducks music beneath narration wherever there is speech.
Master
-14.6 LUFS
Target -14 · on target
True peak -1.0 dBTP
Everyday controls: volume, pan, presence, mute, solo, voice enhancement and noise cleanup.
The four depths of one track
The section tabs are not four tools — they are the same track at increasing specificity. Basic is what everyone touches; EQ is by pitch; Dynamics is consistency over level; Automation is change over time. A user works down that list as their problem gets more specific, which is why the order is fixed and Basic is first.
Voice EQ
Turns parts of the sound up or down by pitch. Each control below is one point on the graph.
24 dB/oct
-2.6 dB
+2.4 dB
+1.8 dB
+1.6 dB
Frequency response: low cut below 80 hertz, warmth down 2.6 decibels at 180 Hz, clarity up 2.4 decibels at 3.0 kHz, presence up 1.8 decibels at 6.0 kHz, air up 1.6 decibels at 12 kHz.
Remove rumble and improve speech clarity.
Voice dynamics
Evens out volume that jumps around — quiet words lifted, loud ones held back, hiss removed between phrases.
Voice leveler
Keeps your voice at a consistent level throughout.
Compressor
Controls loud peaks for a smoother, steadier sound.
De-esser
Tames sharp S sounds without dulling the whole track.
Reducing harsh S sounds by 40%
Noise gate
Silences background noise when nobody is speaking.
Smooth uneven narration and control harsh peaks.
The shell never changes height
All four panels render into the same box at the same height, set once by the tallest of them. That is a hard requirement, not a nicety: these tabs are switched between constantly while listening, and a shell that grows or shrinks moves the tab strip, the track list and the master meters under the pointer every time — so the control you were about to click is somewhere else, and the meters you were watching jump.
Every panel fills a 560px box. The dashed edge is the box.
Music automation
Changes level over time instead of one setting for the whole track — fades, ducking under narration, and lifting individual quiet phrases.
Music auto-duck
Fade in
Bring the track up from silence at the start.
Fade out
Take the track down to silence at the end.
Level quiet sections
Automatically lift phrases that were recorded too quietly.
Create music ducking
Duck the music beneath narration automatically, wherever there is speech.
Level quiet phrases and duck music beneath narration.
An automation lane that works without a mouse
Dragging a dot on a canvas is the fast path, and it is unreachable by keyboard — a canvas cannot take focus and has no value semantics. So selecting a point opens an editor with a real time field, a real level field and a real curve select, and the lane is a picture of that state rather than a separate interface. This is the part most tools skip.
Voice automation
Changes level over time instead of one setting for the whole track — fades, ducking under narration, and lifting individual quiet phrases.
Music auto-duck
Fade in
Bring the track up from silence at the start.
Fade out
Take the track down to silence at the end.
Level quiet sections
Automatically lift phrases that were recorded too quietly.
Create music ducking
Duck the music beneath narration automatically, wherever there is speech.
Level quiet phrases and duck music beneath narration.
Choosing between them
Ask what the user is doing. Setting levels against each other is a console job — eight caps read as one shape. Fixing one track is a studio job, and the console cannot give it the room. A tool that does both usually ships the console as the main view and opens the studio on double-click.
Every channel visible. One shared scale. The balance is the picture.
Media
Storyboard
Narration
Music
Master
Stereo out
0.0 dB
Limiter
One track, five controls, room to label them. The balance is now invisible.
Voice · controls
Compact, for a sidebar
Waveforms and the printed dB scale are the first things to go. The controls keep their size — shrinking a fader to fit is how you get a control nobody can land on.
Why the taper
The same eight values on a tapered scale and a linear one. On the linear track unity gain sits near the top and the useful range is squeezed into the last centimetre.
Interaction states
Every state a user can put this component into, rendered side by side. If a state is missing here, it is missing in production too.
Every part, every measurement, and the reason it is that number.
Media
One strip. Every element sits at the same vertical position on every channel, which is what lets eight of them be read as one picture.
- IdentityNumber + colour + name
Three redundant signals for the same fact. Colour alone fails for a colour-blind user and fails again the moment there are more channels than distinguishable hues.
- Waveform32px, decorative
Recognising a clip by shape is faster than reading its name. It carries nothing the name does not, so it is aria-hidden.
- Gain readoutMono, tabular
The number as text. An engineer writes this down; a fader position cannot be written down. Tabular figures so it does not jitter while dragging.
- Pan44px rotary
A range input presented as a rotation, because a pan value is a position between two speakers. Double-click recentres.
- Mute / Solo28px, aria-pressed
The two controls that change what you hear without moving anything, so they are the two with a lit indicator rather than only a colour change.
- Fader26 × 40px cap
The cap is deliberately large and gripped. It is the control the user reaches for most and the one they aim at while listening rather than looking.
- ScaleMeaningful stops
+12 down to -60, with 0 dB given a physical detent line. Printing every step would make it measuring equipment.
- Meter8px, aria-hidden
Output. Segmented so a level is read as a quantity rather than a colour, with the top of the scale red by position as well as by hue.
- Identity bar4px
A solid block at a fixed position is the fastest index across a row of strips.
Values are read live from the running stylesheet, so this table can never drift from the code. Click any value to copy it.
Color
| Token | Value | Used for |
|---|---|---|
| Surfaces | ||
| --ds-surface-inset | — | The console body behind the strips |
| --ds-surface | — | Channel strip |
| --ds-surface-raised | — | Master strip, and the top of the fader cap gradient |
| --ds-sunken | — | Fader rail and meter well — the recessed parts |
| Interaction | ||
| --ds-layer-hover | — | Mute and solo hover state layer |
| --ds-focus-ring | — | Focus halo on the fader cap and pan knob |
| --ds-border-strong | — | Cap border, grip lines, unity detent |
| Meter | ||
| --ds-success | — | Meter segments up to about -12 dB |
| --ds-warning | — | Meter approaching full scale, and solo |
| --ds-danger | — | Clipping segments, and mute |
| Channel | ||
| Channel hue ramp | Per-channel identity. Not semantic tokens — these are identity hues, the same category as chart series. | |
Spacing
| Token | Value | Used for |
|---|---|---|
| strip width | Narrow enough for eight, wide enough for a 44px pan knob | |
| hit area | Invisible target around the fader rail and the knob |
Radius
| Token | Value | Used for |
|---|---|---|
| --radius-md | — | Channel strip |
Shadow
| Token | Value | Used for |
|---|---|---|
| --shadow-e2 | — | Fader cap, so it reads as sitting on the rail |
Typography
| Token | Value | Used for |
|---|---|---|
| tabular-nums | — | Every dB readout and scale label |
Motion
| Token | Value | Used for |
|---|---|---|
| 75ms | — | Meter segment fade. Never the fader position. |
Pick a size from this table. Do not invent a new one — a fourth height is how a design system starts dying.
| Size | Height | Min width | Max width | Touch target | When to use |
|---|---|---|---|---|---|
| Strip | — | 112px | 160px | — | Below 112px the pan knob drops under the 44px target. Above 160px a full desk stops fitting a laptop. |
| Fader travel | 150px min, 210px default | — | — | — | Under 150px the taper stops buying anything and the useful range collapses. |
| Fader cap | 40px | 26px | — | 44px hit area | Large on purpose — it is aimed at while listening, not while looking. |
| Pan knob | 44px | — | — | 44px | The knob is the target; there is no separate hit area to grow. |
| Mute / Solo | 28px | — | — | — | Two per strip, side by side. Below 28px the pair becomes a mis-click risk on the one control that silences audio. |
| Meter | — | 8px | — | — | Segments must stay square-ish. A meter narrower than 6px reads as a line, not a measurement. |
| Master strip | — | 132px | — | — | Wider than a channel, because it carries two meters and must not be mistaken for channel nine. |
<input type="range" style="writing-mode: vertical-rl; direction: rtl" />Voice · controls
Rumble filter
Everything below this is removed: traffic, air conditioning, desk bumps.
curve = f(bands) — one direction only-14.0 LUFS
Target -14 · on target
Not a checklist to run at the end. These are the requirements the component was built from.
Contrast
- The travelled portion of the rail must reach 3:1 against the untravelled portion — that boundary is the value.
- The fader cap must reach 3:1 against both the rail and the strip behind it, which is why it is a bordered object with its own elevation rather than a coloured block.
- Channel colour is identity, not status, so it is never the only carrier: the number and the name say the same thing.
- Meter segment colours are a convention, not a message. Clipping must also be text.
- Every dB readout is content and owes 4.5:1 — including the coloured one, which is why the channel hue is lightened for text use.
Keyboard
| Tab | Moves through one strip at a time in visual order: pan, mute, solo, fader. Predictable order across strips matters more than any single shortcut. |
| ↑ / ↓ | Moves the focused fader by one step. ← / → move a pan knob. |
| Page Up / Page Down | Larger increment — conventionally 6 dB on a fader. |
| Home / End | Jumps to the minimum or maximum of the focused control. |
| Double-click | Resets a fader to unity and a pan knob to centre. Provide a keyboard equivalent — this must not be the only way. |
| ↑ / ↓ in the track list | Moves the selection in the studio arrangement, which changes everything in the inspector. Tab leaves the list rather than walking it. |
Screen readers
- Announce as "Narration fader, slider, -4.2 dB". The channel name is part of the label, not context the user is expected to remember.
- Never announce meter levels. They change continuously and carry nothing actionable.
- Announce mute and solo as pressed states, and announce the consequence when it is not obvious: "Solo on, 7 channels silenced".
- Group each strip so navigating by group moves channel to channel rather than control to control.
Focus & touch
- The ring sits on the fader cap and the knob face, because those are the parts that move. It must stay visible at both ends of the travel, which means the strip needs padding the halo can occupy — a cap at 0 dB with its focus ring clipped by the strip edge is a keyboard user losing their place.
- The fader rail is 6px but its target is the full 44px strip width; the pan knob is 44px square. On touch the fader is the only control that should respond to a drag — a pan knob that rotates on drag competes with page scroll, so on touch it moves in steps on tap instead.
| Attribute | Applied to | Notes |
|---|---|---|
| role="slider" | Faders and pan knobs | Implicit on input[type=range]. This is the reason to build on it rather than on a div. |
| aria-valuetext | Faders and pan knobs | "-4.2 dB", "L22", "Centre". Without it a pan knob announces "-22" and a fader announces a position on an internal 0–1000 scale that means nothing. |
| aria-orientation="vertical" | Faders | Changes which arrow keys are announced as increasing. |
| aria-label | Every control | Scoped by channel — "Narration fader", not "fader". Eight identically-named sliders is the most common failure in this component. |
| aria-pressed | Mute and solo | They are toggle buttons, not links or checkboxes. The pressed state is the whole meaning. |
| aria-hidden | Meters, waveforms, identity bars | Output and decoration. None of them may be the only source of a fact. |
| role="status" | The clipping indicator | A polite live region, so clipping is announced once rather than sixty times a second. |
| role="listbox" / role="option" | The studio track list | Selection is the navigation model of that arrangement — everything in the inspector belongs to the selected option — so it needs real single-select semantics and roving tabindex, not a row of buttons. |
| aria-selected | Track rows | The selected track must be announced as selected, not merely styled as such. A highlight is invisible to a screen reader. |
Example usage
Two arrangements over the same state. Keep the channel list in one place and let each view render it — a console and a studio holding separate copies is how the two drift apart.
1// Console — every channel at once, for setting a balance2<ConsoleMixer3 channels={channels}4 master={master}5 onChannelChange={(id, patch) => update(id, patch)}6 onMasterChange={setMaster}7/>89// Studio — one selected track in depth, for working on it10<StudioMixer11 tracks={channels}12 selectedId={selectedId}13 onSelect={setSelectedId}14 onTrackChange={(patch) => update(selectedId, patch)}15/>1617// One strip, if you need it alone18<ChannelStrip19 channel={{ id: '1', name: 'Narration', db: 0, pan: 0, mute: false, solo: false }}20 index={0}21 level={peak}22 audible={!muted}23 onChange={(patch) => update('1', patch)}24/>CSS
The vertical fader. `appearance: slider-vertical` is deprecated and never shipped in Firefox; writing-mode is the replacement, and `direction: rtl` is what puts the minimum at the bottom.
.fader input[type="range"] {
writing-mode: vertical-rl;
direction: rtl; /* minimum at the BOTTOM */
inline-size: 100%; /* the 44px target */
block-size: 100%;
opacity: 0; /* the face is painted behind it */
cursor: pointer;
}
/* Never transition the cap's position — it must sit under the pointer. */
.fader__cap {
transition: box-shadow var(--duration-fast);
}Component API
StudioMixer
| Prop | Type | Default | Description |
|---|---|---|---|
| tracks* | Track[] | — | The track list. Same source of truth the console reads — two views holding separate copies is how they drift apart. |
| selectedId* | string | — | Which track the inspector is showing. Controlled, so the host can select a track from elsewhere. |
| onSelect* | (id: string) => void | — | Selection is this arrangement’s whole navigation model. |
| onTrackChange | (patch: Partial<Track>) => void | — | Edits apply to the selected track. No id — the selection already says which. |
| target | number | -14 | Loudness target in LUFS. Printed beside the measured value, because the number alone means nothing to most users. |
ConsoleMixer
| Prop | Type | Default | Description |
|---|---|---|---|
| channels* | Channel[] | — | One entry per strip. Order is the display order. |
| master* | number | — | Master bus gain in dB. |
| onChannelChange | (id: string, patch: Partial<Channel>) => void | — | Called for every control on a strip. Patches rather than whole objects, so a fader drag does not have to know about mute. |
| onMasterChange | (db: number) => void | — | Master fader. |
| compact | boolean | false | Drops the waveform and the printed dB scale. Never shrinks the controls. |
| levels | number[] | — | Live meter levels, 0–1. Omit and the meters stay dark — they are display only. |
Channel
| Prop | Type | Default | Description |
|---|---|---|---|
| id* | string | — | Stable identity. Not the index — strips get reordered. |
| name* | string | — | Used in every aria-label on the strip. |
| db* | number | — | Gain, -60 to +12. -60 is treated as -∞. |
| pan* | number | — | -100 (hard left) to +100 (hard right). 0 is centre. |
| mute* | boolean | — | Silences this channel. |
| solo* | boolean | — | Silences every channel that is not soloed. A mode — surface it. |
Professional tips
- Double-click to reset is the hardware convention and worth honouring — unity on a fader, centre on a pan knob. Give it a keyboard equivalent; it must never be the only route.
- Solo should be exclusive by default and additive with a modifier. Most users want "just this one"; power users want "these three".
- Keep the master strip visually distinct and at the end. A master that looks like channel nine will be dragged as one.
- Peak-hold on the meter — a mark that lingers for a second at the highest recent level — is the single most useful addition, because clipping is over before the eye catches it.
Performance
- Meter updates are the whole performance story. Drive them from one animation frame loop for the entire mixer, not one interval per channel.
- Never route meter levels through the same state as the controls. Sixty updates a second through a store that also owns fader values will re-render every strip.
- Write meter heights directly to the node — a transform or a width — rather than through React state, the way a progress bar does under a drag.
- The waveform thumbnail should be generated once and cached per clip. Recomputing peaks on render is invisible with eight channels and fatal with thirty-two.
Common mistakes
- Eight sliders labelled "slider". The channel name has to be in every aria-label or the whole console is unnavigable by keyboard.
- A meter with no text equivalent, so clipping is invisible to anyone who was not looking at that pixel.
- Solo with no visible consequence — the most reliable source of "the mixer is broken" reports.
- A linear dB scale, which makes the useful range of the fader about a centimetre long.
- Transitioning the fader position, which makes every drag feel broken.
- Colour as the only channel identity, which fails for a colour-blind user and fails for everyone once there are more channels than distinguishable hues.
Real-world recommendations
- Most real tools ship both arrangements: the console as the main view, the studio opened by double-clicking a strip. The console answers "is the balance right", the studio answers "what is wrong with this track", and neither question is rare enough to make the other view optional.
- The studio arrangement is what appears in video editors, where the user is not an audio engineer and there are five tracks rather than forty. It steers towards a loudness target instead of asking for a balance, because "-14 LUFS for this platform" is a decision the tool can make and the user cannot.
- Every hardware console ever built puts the fader at the bottom and the meter beside it. That is not nostalgia — it is because the hand rests low and the eye tracks the meter while the hand moves.
- Broadcast desks give solo its own colour and its own light because an accidental solo on air is a serious failure. The visual weight is proportional to the cost of the mistake.
- DAWs let a strip collapse to a fader and a name and nothing else. That is the honest compact mode: drop information, never target size.