Segmented Control
A radio-group-style toggle bar that renders slotted arc-option elements as a row of mutually exclusive buttons with an active highlight.
<arc-segmented-control>Overview
SegmentedControl is a compact, horizontal set of mutually exclusive options rendered as a pill-shaped button group. It reads <arc-option> children from its default slot and mirrors them as styled buttons inside a bordered container with rounded corners. The currently selected option receives an accent-primary background with a glow, while unselected options appear in muted text that brightens on hover.
The component uses a radiogroup ARIA role with individual radio roles on each option button, following the WAI-ARIA radio group pattern. Keyboard navigation supports arrow keys for cycling through options (with wrapping), Home/End for jumping to the first or last option, and Enter/Space for confirming a selection. Focus management automatically moves focus to the newly selected button after keyboard navigation.
SegmentedControl auto-selects the first option when no initial value is provided, so the control always has a valid selection. It fires a single arc-change event with the selected value whenever the user makes a new choice.
Guidelines
When to use
- Use SegmentedControl for 2-5 options where the user must pick exactly one
- Keep option labels short, ideally one or two words, to prevent overflow
- Provide a `value` attribute if you need to pre-select an option other than the first
- Listen to `arc-change` to react to selection changes in your application logic
- Place the control within a form context or a settings panel where space is limited
When not to use
- Do not use for more than 5 options; use Select or RadioGroup instead for longer lists
- Do not nest interactive elements inside `<arc-option>` children: labels should be plain text
- Do not use SegmentedControl for navigation between views; use Tabs instead
- Do not rely solely on the glow color to indicate selection: the component also uses aria-checked for accessibility
- Avoid using it for binary toggles where a Toggle switch would be more semantically appropriate
Features
- Renders slotted `<arc-option>` elements as styled toggle buttons in a horizontal pill container
- Active option highlighted with accent-primary background, contrasting text, and glow shadow
- Full keyboard navigation: arrow keys cycle options with wrapping, Home/End jump to edges, Enter/Space confirm
- ARIA radiogroup pattern with `role="radio"` and `aria-checked` on each option button
- Auto-selects the first option when no `value` attribute is provided
- Hover state brightens text and adds a subtle background on non-active options
- Disabled state at 40% opacity with pointer events blocked on the entire control
- Respects `prefers-reduced-motion` by disabling transitions
Preview
Usage
This component requires JavaScript. No pure HTML/CSS version is available. Use the Web Component directly or a framework wrapper.
<arc-segmented-control value="monthly">
<arc-option value="daily">Daily</arc-option>
<arc-option value="weekly">Weekly</arc-option>
<arc-option value="monthly">Monthly</arc-option>
</arc-segmented-control>import { SegmentedControl, Option } from '@arclux/arc-ui-react';
export default function Example() {
return (
<SegmentedControl value="monthly">
<Option value="daily">Daily</Option>
<Option value="weekly">Weekly</Option>
<Option value="monthly">Monthly</Option>
</SegmentedControl>
);
}<script setup>
import { SegmentedControl, Option } from '@arclux/arc-ui-vue';
</script>
<template>
<SegmentedControl value="monthly">
<Option value="daily">Daily</Option>
<Option value="weekly">Weekly</Option>
<Option value="monthly">Monthly</Option>
</SegmentedControl>
</template><script>
import { SegmentedControl, Option } from '@arclux/arc-ui-svelte';
</script>
<SegmentedControl value="monthly">
<Option value="daily">Daily</Option>
<Option value="weekly">Weekly</Option>
<Option value="monthly">Monthly</Option>
</SegmentedControl>import { Component } from '@angular/core';
import { SegmentedControl, Option } from '@arclux/arc-ui-angular';
@Component({
imports: [SegmentedControl, Option],
template: `
<arc-segmented-control value="monthly">
<arc-option value="daily">Daily</arc-option>
<arc-option value="weekly">Weekly</arc-option>
<arc-option value="monthly">Monthly</arc-option>
</arc-segmented-control>
`,
})
export class MyComponent {}import { SegmentedControl, Option } from '@arclux/arc-ui-solid';
export default function Example() {
return (
<SegmentedControl value="monthly">
<Option value="daily">Daily</Option>
<Option value="weekly">Weekly</Option>
<Option value="monthly">Monthly</Option>
</SegmentedControl>
);
}import { SegmentedControl, Option } from '@arclux/arc-ui-preact';
export default function Example() {
return (
<SegmentedControl value="monthly">
<Option value="daily">Daily</Option>
<Option value="weekly">Weekly</Option>
<Option value="monthly">Monthly</Option>
</SegmentedControl>
);
}API
valuestring''- The value of the currently selected option. Reflected as an attribute and auto-set to the first selectable option if empty.
namestring''- The form field name submitted with the selected value. Required for native form integration: without it, the selection will not appear in FormData.
disabledbooleanfalse- Disables the entire control, reducing opacity to 40% and blocking pointer events.
formAssociatedbooleantruepropertiesobject{ // flag(), unlike `disabled`. The exclusion in props.js is specifically // about form-associated *platform* semantics: a `disabled` content // attribute that is merely present makes the element actually disabled // per the HTML spec, and formDisabledCallback assigns the property back, // so no converter can win. Neither of these is platform-mapped: // `required` is enforced by _computeValidity() below and `readonly` by // each component's own interaction handlers, so the stock converter buys // nothing here and costs the usual bug: `required="false"` read as true, // blocking submission of a form the author meant to leave optional. // Finding #48's shape, across all 26 form controls at once. required: flag(false), readonly: flag(false), }- Lit merges static properties up the prototype chain, so every consumer
gets these without declaring them.
requiredparticipates in constraint validation below;readonlyreflects for styling and is enforced by each component's interaction handlers (the mixin can't know which gestures mutate state). autoValidatesbooleantrue- Components that run their own constraint-validation logic (pattern checks, range checks) opt out of the automatic required sync by overriding this to false, and own the whole validity flag set instead.
formvalidityvalidationMessagerequiredbooleanfalsereadonlybooleanfalse
Methods
checkValidity()boolean- Whether the control currently satisfies its constraints, per the native
constraint-validation API. Fires
invalidon the element when it does not, and reports nothing to the user. reportValidity()boolean- As checkValidity(), but also shows the browser's validation message against the control when it fails.
Events
arc-changedetail:{ value: string }- Fired when the selected segment changes
Option
<arc-option>Individual option element slotted into the segmented control. The `value` attribute identifies the option and the text content becomes the label.
label- Expose text content as label
valuestring''- The value identifier for this option, used to match against the parent control value.
disabledbooleanfalse- When true, dims this option and prevents it from being selected.
selectedbooleanfalse
See Also
- TabsTabbed content navigation with keyboard support and ARIA roles.
- Radio GroupSingle-select option group with arrow-key navigation and ARIA radiogroup semantics. Fits pricing tiers, settings panels, and any place where exactly one choice must be made from a visible set of options.
- ChipA toggleable pill-shaped element for filters, tags, or multi-select options, with a selected state highlighted in accent-primary.