Theming
Waveform Playlist includes a comprehensive theming system built on styled-components, allowing full visual customization.
Theme Architecture
The theme system follows a single source of truth pattern:
WaveformPlaylistTheme interface defines all theme properties
defaultTheme and darkTheme provide built-in presets
Components access theme via styled-components ThemeProvider
No color props on components—all styling through theme
Here’s the complete theme interface from packages/ui-components/src/wfpl-theme.ts:
interface WaveformPlaylistTheme {
// Waveform drawing mode
waveformDrawMode ?: 'inverted' | 'normal' ;
// Waveform colors (can be solid colors or gradients)
waveOutlineColor : WaveformColor ;
waveFillColor : WaveformColor ;
waveProgressColor : string ;
// Selected track colors
selectedWaveOutlineColor : WaveformColor ;
selectedWaveFillColor : WaveformColor ;
selectedTrackControlsBackground : string ;
// Timescale colors
timeColor : string ;
timescaleBackgroundColor : string ;
// Playback UI colors
playheadColor : string ;
selectionColor : string ;
// Loop region colors (Audacity-style)
loopRegionColor : string ;
loopMarkerColor : string ;
// Clip header colors
clipHeaderBackgroundColor : string ;
clipHeaderBorderColor : string ;
clipHeaderTextColor : string ;
clipHeaderFontFamily : string ;
selectedClipHeaderBackgroundColor : string ;
// Fade overlay
fadeOverlayColor : string ;
// UI component colors
backgroundColor : string ;
surfaceColor : string ;
borderColor : string ;
textColor : string ;
textColorMuted : string ;
// Interactive elements
inputBackground : string ;
inputBorder : string ;
inputText : string ;
inputPlaceholder : string ;
inputFocusBorder : string ;
// Buttons
buttonBackground : string ;
buttonText : string ;
buttonBorder : string ;
buttonHoverBackground : string ;
// Sliders
sliderTrackColor : string ;
sliderThumbColor : string ;
// Annotations
annotationBoxBackground : string ;
annotationBoxActiveBackground : string ;
annotationBoxHoverBackground : string ;
annotationBoxBorder : string ;
annotationBoxActiveBorder : string ;
annotationLabelColor : string ;
annotationResizeHandleColor : string ;
annotationResizeHandleActiveColor : string ;
annotationTextItemHoverBackground : string ;
// Spacing and sizing
borderRadius : string ;
fontFamily : string ;
fontSize : string ;
fontSizeSmall : string ;
}
Built-in Themes
Default Theme (Light Mode)
import { defaultTheme } from '@waveform-playlist/ui-components' ;
const theme = {
waveformDrawMode: 'inverted' ,
waveOutlineColor: '#ffffff' ,
waveFillColor: '#1a7f8e' , // Teal
waveProgressColor: 'rgba(0, 0, 0, 0.10)' ,
playheadColor: '#f00' , // Red
selectionColor: 'rgba(255, 105, 180, 0.7)' , // Hot pink
// ... more properties
};
Dark Theme
The dark theme uses the Ampelmännchen Traffic Light color palette inspired by the iconic DDR pedestrian signal.
import { darkTheme } from '@waveform-playlist/ui-components' ;
const theme = {
waveformDrawMode: 'inverted' ,
waveOutlineColor: '#c49a6c' , // Warm amber
waveFillColor: '#1a1612' , // Dark warm brown
playheadColor: '#3a8838' , // Ampelmännchen green
selectionColor: 'rgba(60, 140, 58, 0.6)' , // Green (visible on dark)
buttonBackground: '#63C75F' , // Official Ampelmännchen brand green
buttonText: '#0a0a0f' , // Black text
// ... more properties
};
Dark Theme Color Palette:
🟢 Green (#63C75F) - Official Ampelmännchen brand green for buttons/links
🟡 Amber (#c49a6c) - Warm golden waveform bars and body text
🔴 Red (#d08070) - Headings and accent elements
Using Themes
Basic Usage
Pass a theme to the provider:
import {
WaveformPlaylistProvider ,
Waveform ,
} from '@waveform-playlist/browser' ;
import { darkTheme } from '@waveform-playlist/ui-components' ;
function App () {
return (
< WaveformPlaylistProvider
tracks = { tracks }
theme = { darkTheme } // Apply dark theme
>
< Waveform />
</ WaveformPlaylistProvider >
);
}
Custom Theme
Extend or override the default theme:
import { defaultTheme } from '@waveform-playlist/ui-components' ;
const customTheme = {
... defaultTheme ,
waveOutlineColor: '#000000' ,
waveFillColor: '#3498db' ,
playheadColor: '#e74c3c' ,
selectionColor: 'rgba(46, 204, 113, 0.5)' ,
};
< WaveformPlaylistProvider theme = { customTheme } tracks = { tracks } >
< Waveform />
</ WaveformPlaylistProvider >
You only need to specify properties you want to override. Unspecified properties fall back to defaultTheme.
Partial Theme
The provider accepts Partial<WaveformPlaylistTheme>:
< WaveformPlaylistProvider
tracks = { tracks }
theme = {{
playheadColor : '#00ff00' ,
selectionColor : 'rgba(0, 255, 0, 0.3)' ,
}}
>
< Waveform />
</ WaveformPlaylistProvider >
Waveform colors support gradients for advanced visual effects:
import type { WaveformGradient } from '@waveform-playlist/ui-components' ;
const gradientTheme = {
... defaultTheme ,
waveFillColor: {
type: 'linear' ,
direction: 'vertical' , // or 'horizontal'
stops: [
{ offset: 0 , color: '#667eea' }, // Purple at top
{ offset: 0.5 , color: '#764ba2' }, // Mid purple
{ offset: 1 , color: '#f093fb' }, // Pink at bottom
],
} as WaveformGradient ,
};
Gradients work with both waveFillColor and waveOutlineColor properties.
The theme includes a waveformDrawMode option that controls how colors are applied:
Inverted Mode (Default)
Canvas draws waveOutlineColor in areas without audio:
const theme = {
waveformDrawMode: 'inverted' ,
waveOutlineColor: '#ffffff' , // White background
waveFillColor: '#1a7f8e' , // Teal shows through peaks
};
Use inverted mode for: Gradient bars, colored peaks on solid backgrounds
Normal Mode
Canvas draws waveFillColor bars where audio peaks are :
const theme = {
waveformDrawMode: 'normal' ,
waveOutlineColor: '#000000' , // Black background
waveFillColor: '#00ff00' , // Green bars drawn on peaks
};
Use normal mode for: Gradient backgrounds, solid-colored peaks
Selected Track Styling
Differentiate the selected track with dedicated theme properties:
const theme = {
... defaultTheme ,
// Normal tracks
waveFillColor: '#1a7f8e' ,
// Selected track (brighter)
selectedWaveFillColor: '#00b4d8' ,
selectedWaveOutlineColor: '#ffffff' ,
selectedTrackControlsBackground: '#d9e9ff' ,
selectedClipHeaderBackgroundColor: '#b3d9ff' ,
};
Selected track gets automatically highlighted when you click it or use editing operations (move, trim, split).
Accessing Theme in Components
Components access theme via styled-components:
import styled from 'styled-components' ;
const PlayheadLine = styled . div `
position: absolute;
width: 2px;
height: 100%;
background-color: ${ props => props . theme . playheadColor } ;
pointer-events: none;
` ;
Type Safety
The theme is fully typed via module augmentation:
// packages/ui-components/src/styled.d.ts
import 'styled-components' ;
import { WaveformPlaylistTheme } from './wfpl-theme' ;
declare module 'styled-components' {
export interface DefaultTheme extends WaveformPlaylistTheme {}
}
This gives you full autocomplete for theme properties in styled components.
Annotation Theming
Annotations have dedicated theme properties:
const theme = {
... defaultTheme ,
annotationBoxBackground: 'rgba(255, 255, 255, 0.85)' ,
annotationBoxActiveBackground: 'rgba(255, 255, 255, 0.95)' ,
annotationBoxBorder: '#ff9800' , // Orange border
annotationBoxActiveBorder: '#d67600' , // Darker when active
annotationLabelColor: '#2a2a2a' ,
annotationResizeHandleColor: 'rgba(0, 0, 0, 0.4)' ,
};
Dynamic Theme Switching
Switch themes at runtime:
import { useState } from 'react' ;
import { defaultTheme , darkTheme } from '@waveform-playlist/ui-components' ;
function App () {
const [ isDark , setIsDark ] = useState ( false );
return (
<>
< button onClick = {() => setIsDark (! isDark )} >
Toggle Theme
</ button >
< WaveformPlaylistProvider
tracks = { tracks }
theme = {isDark ? darkTheme : defaultTheme }
>
< Waveform />
</ WaveformPlaylistProvider >
</>
);
}
Docusaurus Integration
Detect Docusaurus theme changes:
import { useEffect , useState } from 'react' ;
import { defaultTheme , darkTheme } from '@waveform-playlist/ui-components' ;
function useDocusaurusTheme () {
const [ theme , setTheme ] = useState ( defaultTheme );
useEffect (() => {
// Check initial theme
const isDark = document . documentElement . dataset . theme === 'dark' ;
setTheme ( isDark ? darkTheme : defaultTheme );
// Observe theme changes
const observer = new MutationObserver (() => {
const isDark = document . documentElement . dataset . theme === 'dark' ;
setTheme ( isDark ? darkTheme : defaultTheme );
});
observer . observe ( document . documentElement , {
attributes: true ,
attributeFilter: [ 'data-theme' ],
});
return () => observer . disconnect ();
}, []);
return theme ;
}
// Usage
function App () {
const theme = useDocusaurusTheme ();
return (
< WaveformPlaylistProvider tracks = { tracks } theme = { theme } >
< Waveform />
</ WaveformPlaylistProvider >
);
}
Theme Organization
When to Add to Theme
✅ Visual/styling properties (colors, backgrounds, borders)
✅ User-customizable aesthetics
✅ Properties shared across components
When to Use Separate Props
✅ Functional/behavioral properties (callbacks, data, configuration)
✅ Properties that control what is rendered
✅ Component-specific settings
Example:
// ✅ Good: Visual in theme, behavior as prop
< Waveform
showClipHeaders = { true } // Behavior prop
// clipHeaderBackgroundColor is in theme
/>
// ❌ Bad: Mixing visual and behavioral props
< Waveform
showClipHeaders = { true }
clipHeaderBackgroundColor = "#333" // Should be in theme
/>
CSS Variables Alternative
For non-React styling, the theme can be converted to CSS variables:
function themeToCSSVariables ( theme : WaveformPlaylistTheme ) {
return Object . entries ( theme )
. map (([ key , value ]) => {
const cssKey = key . replace ( / ( [ A-Z ] ) / g , '-$1' ). toLowerCase ();
return `--waveform- ${ cssKey } : ${ value } ;` ;
})
. join ( ' \n ' );
}
// Apply to :root
const cssVars = themeToCSSVariables ( darkTheme );
document . documentElement . style . cssText = cssVars ;
Changing the theme triggers a full re-render of all components. Avoid frequent theme switches during playback or intensive operations.
Optimization Tips
Set theme once at the provider level
Avoid inline theme objects (creates new reference every render)
Use useMemo for computed theme values:
const theme = useMemo (() => ({
... defaultTheme ,
playheadColor: userColor ,
}), [ userColor ]);
Theming Best Practices
Name theme properties by their purpose (playheadColor) rather than appearance (redColor). This makes themes easier to understand and maintain.
Test Dark and Light Modes
Always test your custom theme in both light and dark environments. What works on white may not work on black.
Maintain Sufficient Contrast
Ensure text and UI elements have sufficient contrast (WCAG AA: 4.5:1 for normal text, 3:1 for large text).
Use Alpha Transparency Sparingly
Overlays (selection, loop region, annotations) use alpha transparency to blend with the background. Too much transparency makes them hard to see.
Next Steps
Architecture Understand the overall system design
Styling Example See theming in action
API Reference Complete type definitions
UI Components Explore available components