GlassSegmentedControl class

A glass morphism segmented control following Apple's design patterns.

GlassSegmentedControl provides a sophisticated segmented control with an animated glass indicator, jelly physics, and smooth transitions between segments. It matches iOS's UISegmentedControl appearance and behavior.

Key Features

  • Animated Glass Indicator: Smoothly animates between segments
  • Jelly Physics: Organic squash and stretch effects during movement
  • Drag Support: Swipe between segments with velocity-based snapping
  • Sharp Text: Selected text stays sharp above the glass
  • Flexible Sizing: Automatically sizes segments evenly
  • Customizable Appearance: Full control over colors, sizes, and effects

⚠️ Do Not Wrap in GlassContainer or GlassCard

Placing this widget inside a GlassContainer or GlassCard is an anti-pattern that causes two problems:

  1. Visual degradation — the container sets avoidsRefraction: true on its children, causing the indicator's glass refraction to fall back to a non-refracting path.
  2. Jelly-physics clipping — with useOwnLayer: true on the container, the container's own-layer clip cuts the indicator's jelly overshoot during drag animations.

The track background is already handled by backgroundColor — no outer container is needed. For a standalone glass layer, use useOwnLayer on this widget directly.

Usage

Basic Usage

int selectedIndex = 0;

GlassSegmentedControl(
  segments: const [
    GlassSegment(label: 'Daily'),
    GlassSegment(label: 'Weekly'),
    GlassSegment(label: 'Monthly'),
  ],
  selectedIndex: selectedIndex,
  onSegmentSelected: (index) {
    setState(() => selectedIndex = index);
  },
)

Within LiquidGlassLayer (Grouped Mode)

AdaptiveLiquidGlassLayer(
  settings: LiquidGlassSettings(
    thickness: 30,
    blur: 3,
    refractiveIndex: 1.59,
  ),
  child: Column(
    children: [
      GlassSegmentedControl(
        segments: const [
          GlassSegment(label: 'One'),
          GlassSegment(label: 'Two'),
          GlassSegment(label: 'Three'),
        ],
        selectedIndex: _selectedIndex,
        onSegmentSelected: (index) {
          setState(() => _selectedIndex = index);
        },
      ),
    ],
  ),
)

Standalone Mode

GlassSegmentedControl(
  segments: const [
    GlassSegment(label: 'Option A'),
    GlassSegment(label: 'Option B'),
  ],
  selectedIndex: _selectedIndex,
  onSegmentSelected: (index) {
    setState(() => _selectedIndex = index);
  },
  useOwnLayer: true,
  settings: LiquidGlassSettings(
    thickness: 30,
    blur: 3,
  ),
)

Icons and labels

GlassSegmentedControl(
  segments: const [
    GlassSegment(icon: Icon(Icons.photo),     label: 'Photos'),
    GlassSegment(icon: Icon(Icons.videocam),  label: 'Videos'),
    GlassSegment(icon: Icon(Icons.music_note),label: 'Music'),
  ],
  selectedIndex: _selectedIndex,
  onSegmentSelected: (index) =>
      setState(() => _selectedIndex = index),
  height: 56,
)

Custom Styling

GlassSegmentedControl(
  segments: const [
    GlassSegment(label: 'Small'),
    GlassSegment(label: 'Medium'),
    GlassSegment(label: 'Large'),
  ],
  selectedIndex: _selectedIndex,
  onSegmentSelected: (index) =>
      setState(() => _selectedIndex = index),
  height: 36,
  borderRadius: 18,
  selectedTextStyle: TextStyle(
    fontSize: 14,
    fontWeight: FontWeight.w600,
    color: CupertinoColors.white,
  ),
  unselectedTextStyle: TextStyle(
    fontSize: 14,
    fontWeight: FontWeight.w500,
    color: CupertinoColors.white.withOpacity(0.6),
  ),
)

Vertical icon control

GlassSegmentedControl(
  direction: Axis.vertical,
  height: 44, // cross-axis width in vertical mode
  segmentExtent: 52,
  segments: const [
    GlassSegment(
      icon: Icon(CupertinoIcons.square_grid_2x2),
      semanticLabel: 'Canvas',
    ),
    GlassSegment(
      icon: Icon(CupertinoIcons.circle_grid_hex),
      semanticLabel: 'Flow',
    ),
  ],
  selectedIndex: selectedIndex,
  onSegmentSelected: onSelected,
)
Inheritance

Constructors

GlassSegmentedControl({required List<GlassSegment> segments, required int selectedIndex, required ValueChanged<int> onSegmentSelected, Key? key, double height = GlassDefaults.heightControl, double borderRadius = GlassDefaults.capsuleRadius, double? indicatorBorderRadius, EdgeInsetsGeometry padding = const EdgeInsets.all(2), TextStyle? selectedTextStyle, TextStyle? unselectedTextStyle, Color? backgroundColor, Color? indicatorColor, LiquidGlassSettings? indicatorSettings, double indicatorPinchStrength = 0.4, EdgeInsetsGeometry indicatorExpansion = const EdgeInsets.symmetric(horizontal: 12, vertical: 8), LiquidGlassSettings? settings, bool useOwnLayer = false, GlassQuality? quality, GlobalKey<State<StatefulWidget>>? backgroundKey, ScrollController? scrollController, SegmentSelectionAlignment selectionAlignment = SegmentSelectionAlignment.minimal, Duration regridDuration = Duration.zero, SegmentDragBehavior dragBehavior = SegmentDragBehavior.selectIndicator, Axis direction = Axis.horizontal, double? segmentExtent, GlassInteractionBehavior interactionBehavior = GlassInteractionBehavior.full, Color? glowColor, double glowRadius = 1.5, bool isScrollable = false, double iconSize = 24.0, EdgeInsetsGeometry labelPadding = const EdgeInsets.symmetric(horizontal: 16), Color? selectedIconColor, Color? unselectedIconColor, MaskingQuality maskingQuality = MaskingQuality.high, DividerSettings? dividerSettings, List<BoxShadow>? indicatorShadow})
Creates a fixed-extent glass segmented control (iOS UISegmentedControl).
const
GlassSegmentedControl.scrollable({required List<GlassSegment> segments, required int selectedIndex, required ValueChanged<int> onSegmentSelected, Key? key, double height = 44.0, double borderRadius = GlassDefaults.capsuleRadius, double? indicatorBorderRadius, EdgeInsetsGeometry padding = const EdgeInsets.all(2), TextStyle? selectedTextStyle, TextStyle? unselectedTextStyle, Color? backgroundColor, Color? indicatorColor, LiquidGlassSettings? indicatorSettings, double indicatorPinchStrength = 0.4, EdgeInsetsGeometry indicatorExpansion = const EdgeInsets.symmetric(horizontal: 12, vertical: 8), LiquidGlassSettings? settings, bool useOwnLayer = false, GlassQuality? quality, GlobalKey<State<StatefulWidget>>? backgroundKey, ScrollController? scrollController, SegmentSelectionAlignment selectionAlignment = SegmentSelectionAlignment.minimal, Duration regridDuration = Duration.zero, SegmentDragBehavior dragBehavior = SegmentDragBehavior.selectIndicator, double iconSize = 24.0, EdgeInsetsGeometry labelPadding = const EdgeInsets.symmetric(horizontal: 16), Color? selectedIconColor, Color? unselectedIconColor, MaskingQuality maskingQuality = MaskingQuality.high, DividerSettings? dividerSettings, List<BoxShadow>? indicatorShadow})
Creates a scrollable glass segmented control that 100% mimics GlassTabBar(isScrollable: true) from the original API.
const

Properties

backgroundColor → Color?
Background color of the segmented control.
final
backgroundKey → GlobalKey<State<StatefulWidget>>?
Optional background key for Skia/Web refraction.
final
borderRadius → double
Border radius of the segmented control.
final
direction → Axis
The axis along which fixed-mode segments are laid out.
final
dividerSettings → DividerSettings?
Optional divider settings between segments. Scrollable mode only.
final
dragBehavior → SegmentDragBehavior
What a horizontal drag means. Scrollable mode only — the fixed control always drags its indicator (UISegmentedControl parity).
final
glowColor → Color?
Colour of the press/drag glow on the indicator pill.
final
glowRadius → double
Spread radius of the glow relative to the indicator's shorter dimension.
final
hashCode → int
The hash code for this object.
no setterinherited
height → double
Height of the segmented control.
final
iconSize → double
Icon size in logical pixels. Used in scrollable mode only. Defaults to 24.0 — matching GlassTabBar.
final
indicatorBorderRadius → double?
Optional override for the active indicator's corner radius.
final
indicatorColor → Color?
Color of the indicator when not being dragged.
final
indicatorExpansion → EdgeInsetsGeometry
Expansion padding applied to the active indicator pill during interaction.
final
indicatorPinchStrength → double
Maximum concave lens pinch strength for the sliding indicator pill.
final
indicatorSettings → LiquidGlassSettings?
Glass settings for the draggable indicator.
final
indicatorShadow → List<BoxShadow>?
Optional box shadows on the pill indicator. Scrollable mode only.
final
interactionBehavior → GlassInteractionBehavior
Controls which iOS 26 interaction effects are active on the indicator.
final
isScrollable → bool
Whether this control scrolls horizontally.
final
key → Key?
Controls how one widget replaces another widget in the tree.
finalinherited
labelPadding → EdgeInsetsGeometry
Horizontal padding inside each tab label cell. Scrollable mode only.
final
maskingQuality → MaskingQuality
Masking quality for the dual-layer icon rendering. Scrollable mode only.
final
onSegmentSelected → ValueChanged<int>
Called when a segment is selected.
final
padding → EdgeInsetsGeometry
Padding around the indicator inside the background.
final
quality → GlassQuality?
Rendering quality for the glass effect.
final
regridDuration → Duration
Duration of the re-grid morph when the segment LIST changes around a surviving selection (scrollable mode only): entering segments grow in (width, with a slight scale and fade riding it), leaving segments shrink out, and the survivors glide — anchored so the selected segment does not move on screen.
final
runtimeType → Type
A representation of the runtime type of the object.
no setterinherited
scrollController → ScrollController?
External scroll controller for the scrollable variant. Scrollable mode only.
final
segmentExtent → double?
Main-axis size of each segment in vertical mode.
final
segments → List<GlassSegment>
List of segments to display.
final
selectedIconColor → Color?
Icon color for the selected segment. Scrollable mode only. Defaults to the primary label color.
final
selectedIndex → int
Index of the currently selected segment.
final
selectedTextStyle → TextStyle?
Text style for the selected segment.
final
selectionAlignment → SegmentSelectionAlignment
Where the scrollable variant keeps its selected segment. Scrollable mode only.
final
settings → LiquidGlassSettings?
Glass effect settings (only used when useOwnLayer is true).
final
unselectedIconColor → Color?
Icon color for unselected segments. Scrollable mode only. Defaults to the secondary label color.
final
unselectedTextStyle → TextStyle?
Text style for unselected segments.
final
useOwnLayer → bool
Whether to create its own layer or use grouped glass.
final

Methods

createElement() → StatefulElement
Creates a StatefulElement to manage this widget's location in the tree.
inherited
createState() → State<GlassSegmentedControl>
Creates the mutable state for this widget at a given location in the tree.
override
debugDescribeChildren() → List<DiagnosticsNode>
Returns a list of DiagnosticsNode objects describing this node's children.
inherited
debugFillProperties(DiagnosticPropertiesBuilder properties) → void
Add additional properties associated with the node.
inherited
noSuchMethod(Invocation invocation) → dynamic
Invoked when a nonexistent method or property is accessed.
inherited
toDiagnosticsNode({String? name, DiagnosticsTreeStyle? style}) → DiagnosticsNode
Returns a debug representation of the object that is used by debugging tools and by DiagnosticsNode.toStringDeep.
inherited
toString({DiagnosticLevel minLevel = DiagnosticLevel.info}) → String
A string representation of this object.
inherited
toStringDeep({String prefixLineOne = '', String? prefixOtherLines, DiagnosticLevel minLevel = DiagnosticLevel.debug, int wrapWidth = 65}) → String
Returns a string representation of this node and its descendants.
inherited
toStringShallow({String joiner = ', ', DiagnosticLevel minLevel = DiagnosticLevel.debug}) → String
Returns a one-line detailed description of the object.
inherited
toStringShort() → String
A short, textual description of this widget.
inherited

Operators

operator ==(Object other) → bool
The equality operator.
inherited