CycleButton
A discrete interaction control that cycles through a set of options with support for custom visual content and multiple operation modes.
The CycleButton component provides a discrete interaction control that cycles through a set of options. It shares the same layout, parameter model, interaction, and accessibility as other vector components; see the Vector introduction.
- Multiple visual variants: abstract, simplest, plainCap, iconCap
- Four modes of operation: Ad-hoc (children), strict (parameter), hybrid, and options prop
- Full theming support: color, roundness, thickness customization
Important: This control supports discrete interaction only (click to cycle, keyboard to step). It does not support continuous interaction (drag/wheel).
Props
| Name | Type | Default | Required | Description |
|---|---|---|---|---|
value | string | number | — | No | Current value of the control (Controlled mode) |
defaultValue | string | number | — | No | Default value of the component (Uncontrolled mode) |
onChange | (event: AudioControlEvent<string | number>) => void | — | No | Handler for value changes |
label | string | — | No | Label displayed below the component |
options | DiscreteOption[] | — | No | Option definitions for the parameter model (Ad-Hoc mode). Defines the parameter structure (value, label, midiValue). Does NOT provide visual content - use children (OptionView components) for that. |
children | React.ReactNode | — | No | Child elements (OptionView components) for visual content mapping. Provides ReactNodes for rendering (icons, text, custom components). Does NOT define the parameter model - use options prop or parameter prop for that. |
renderOption | (option: { value: string | number; label: string }) => React.ReactNode | — | No | Custom renderer for options (used when parameter is provided but no children map exists) |
thickness | number | 0.4 | No | Thickness of the stroke (normalized 0.0-1.0, maps to 1-20). Used by rotary variant. |
openness | number | 90 | No | Openness of the ring in degrees (0-360º; 0º closed, 90º 3/4 open, 180º half-circle) |
rotation | number | 0 | No | Optional rotation angle offset in degrees |
color | string | — | No | Component primary color - any valid CSS color value |
roundness | number | — | No | Roundness for component corners (normalized 0.0-1.0) |
adaptiveSize | boolean | false | No | Whether the component stretches to fill its container |
size | "xsmall" | "small" | "normal" | "large" | "xlarge" | normal | No | Size of the component |
displayMode | "scaleToFit" | "fill" | scaleToFit | No | Layout mode for the control |
labelMode | "none" | "hidden" | "visible" | visible | No | Visibility of the label row |
labelPosition | "above" | "below" | below | No | Vertical position of the label relative to the control |
labelAlign | "start" | "center" | "end" | center | No | Horizontal alignment of the label text |
labelOverflow | "ellipsis" | "abbreviate" | "auto" | auto | No | How to handle label text overflow |
labelHeightUnits | number | — | No | Label height in the same units as SVG viewBox height |
parameter | DiscreteParameter | — | No | Audio Parameter definition (Model). If provided, overrides label/options from ad-hoc props |
paramId | string | — | No | Identifier for the parameter this control represents |
midiResolution | MidiResolution | 7 | No | MIDI resolution in bits (ad-hoc mode, ignored if parameter provided) |
midiMapping | "spread" | "sequential" | "custom" | spread | No | MIDI mapping strategy (ad-hoc mode, ignored if parameter provided) |
onClick | React.MouseEventHandler | — | No | Click event handler |
onMouseDown | React.MouseEventHandler | — | No | Mouse down event handler |
onMouseUp | React.MouseEventHandler | — | No | Mouse up event handler |
onMouseEnter | React.MouseEventHandler | — | No | Mouse enter event handler |
onMouseLeave | React.MouseEventHandler | — | No | Mouse leave event handler |
className | string | — | No | Additional CSS classes |
style | React.CSSProperties | — | No | Additional inline styles |
Understanding Options vs Children
The CycleButton component distinguishes between two concepts:
optionsprop: Defines the parameter model (value, label, midiValue). Used for parameter structure.children(OptionView components): Provides visual content (ReactNodes) for rendering. Used for display.
These serve different purposes and can be used together:
- Use
optionswhen you have data-driven option definitions - Use
childrenwhen you want to provide custom visual content (icons, styled text, etc.) - Use both:
optionsfor the model,childrenfor visuals (matched by value)
Note:
Basic use: For basic use of CycleButton, and especially when using ad-hoc props (no parameter prop), prefer OptionView children. In that case the option set is inferred from the OptionViews: you declare each option once as <OptionView value="...">...</OptionView>, and the same children are used for display. Use the options prop or a full parameter when you need explicit parameter structure (e.g. data-driven options, MIDI mapping). For a full disambiguation of options vs OptionView, see Options vs OptionView.
Modes of Operation
The CycleButton component supports four modes of operation:
1. Ad-Hoc Mode (Options prop)
Model from options prop, visual from children (if provided) or default rendering:
2. Ad-Hoc Mode (Children only)
Model inferred from OptionView children, visual from children:
3. Strict Mode (Parameter only)
Model from parameter prop, visual via renderOption callback. The following example uses children (OptionView); with the full library you would use createDiscreteParameter and the parameter prop:
4. Hybrid Mode (Parameter + Children)
Model from parameter prop, visual from children (matched by value):
Using OptionView
The OptionView component is used to provide visual content for each option. It accepts any ReactNode as children:
Text Content
Icon Content
Custom Components
Theming
Vector components share common theming features; see Theming Features in the Vector introduction for an overview. The CycleButton component supports theming through the color and roundness props:
Color Customization
Roundness
Control the roundness of the knob ring:
Thickness
Adjust the stroke thickness:
Ring Configuration
Openness
Control how much of the ring is visible:
Rotation
Offset the starting angle of the ring:
Adaptive Sizing
The CycleButton component supports both fixed sizes and adaptive sizing. By default, the size of the component is driven by the Size System and their size attribute:
Adaptive sizing allows the component to adapt to the size of its parent container, adjusting its dimensions intelligently without causing distortion (scaleToFit display mode). The fill display mode makes the component fill the entire container, potentially distorting it.
Controlled vs Uncontrolled
The CycleButton supports both controlled and uncontrolled modes:
Controlled Mode
Uncontrolled Mode
MIDI Integration
When using the options prop or parameter model, you can specify MIDI values for each option:
Common Patterns
Waveform Selector
A typical waveform selector for synthesizers:
Filter Type Selector
A filter type selector:
Common Use Cases
The Waveform Selector and Filter Type Selector examples above (in Common Patterns) show typical use cases with live demos.
Next Steps
- Explore the Button component for boolean controls
- Learn about Knob and Slider for continuous controls
- Check out FilmStripDiscreteControl or ImageRotarySwitch for bitmap-based discrete controls
- Visit the playground for interactive examples