Skip to main content

SegmentedControl

SegmentedControl is the compact, always-visible alternative to a dropdown for a small set of mutually exclusive choices.

It works well when the user should be able to compare the available modes at a glance, such as switching between tabs, filters, or display modes. It is not a good fit for large or dynamic option sets.

Example

use fission::prelude::*;
use std::sync::Arc;

let node = SegmentedControl {
options: vec!["All".into(), "Unread".into(), "Flagged".into()],
selected_index: view.state.filter_mode,
on_change: Some(Arc::new(move |index| ActionEnvelope {
id: set_filter_mode_id,
payload: serde_json::to_vec(&SetFilterMode(index)).unwrap(),
})),
}
.build(ctx, view);

Each segment is just a button under the hood. The selected segment is derived from state on every build.

Field table

FieldTypeMeaningNotes / default behavior
optionsVec<String>Labels for each segment.Required. Keep the set short.
selected_indexusizeWhich option is currently active.Controlled by app state.
on_changeOption<Arc<dyn Fn(usize) -> ActionEnvelope + Send + Sync>>Closure that creates the action for a newly chosen index.Called once per segment during build to prepare that segment's action.

How it fits the state loop

The closure returns an ActionEnvelope for the chosen index. When the user presses a segment, that action is dispatched, the reducer updates the selected index in AppState, and the next build re-renders the control with the new active segment.

Because the closure runs during build() to prepare per-segment actions, keep it cheap and deterministic. It should package data, not do work.

Specific advice

Use segmented controls for short, peer-level choices. If labels start wrapping, if there are more than a handful of options, or if options may disappear behind scrolling, you probably want Select or Radio instead.

Radio, Select, Button, and Tabs.