g1455

Switch

GlassSwitch is the iOS 26 on/off switch: a white knob that lifts into a clear glass drop while you press or drag it, and costs nothing extra at rest.

GlassSwitch is an on/off switch drawn the way iOS 26 draws it: a 64 × 28 track and a white knob. When you press the knob, it lifts into a clear glass drop about 1.57 times its size, which bends what is under it. Drag it to the other side, or just tap; let go and the value commits.

At rest the knob and the track are ordinary paint, not glass. The drop exists only while the switch is held, so a page with twenty switches costs the glass nothing until someone touches one.

When to use

  • Boolean settings: Wi-Fi on or off, notifications, a feature flag in a settings group.
  • Over imagery or on a Card, where an ordinary switch would look flat.
  • Not for choosing between more than two options: use a segmented control.
  • Not for an action that happens right away and can't be undone. That is a button.

Usage

The switch is controlled: you hold the value and pass it back in.

bool _wifi = true;

GlassSwitch(
  value: _wifi,
  onChanged: (bool v) => setState(() => _wifi = v),
)

Pass onChanged: null to disable it. A disabled switch is drawn at kGlassDisabledOpacity (50%).

Accessibility

The switch reports itself as a toggle with its state. It has no label of its own. Either pass a semanticLabel, or put it in a row with a Text and wrap the row in MergeSemantics, so a screen reader says "Wi-Fi, switch, on" as one item:

MergeSemantics(
  child: Row(
    children: <Widget>[
      const Expanded(child: Text('Wi-Fi')),
      GlassSwitch(value: wifi, onChanged: onWifi),
    ],
  ),
)

The switch is never smaller than 44 px tall, so it stays easy to hit.

The drop

  • dropScale (default kGlassDropScale, 1.57) is how much bigger the held drop is than the knob. It must be at least 1.
  • dropWiden (default kGlassSwitchDropWiden, 5 px) makes the drop show a little more of what is around it, which looks slightly zoomed out, as on iOS. 0 turns that off.
  • The drop's refraction is kGlassDropOptics, and it grows in over kGlassDropDuration (180 ms).

Note

The drop needs a GlassHost above it to be glass. Without one the switch still works, but the held knob is not drawn as glass.

Code

import 'package:flutter/material.dart';
import 'package:g1455/g1455.dart';

/// A settings group on a glass card.
/// Assumes a GlassHost above the navigator (MaterialApp.builder).
class ConnectivitySettings extends StatefulWidget {
  const ConnectivitySettings({super.key});

  @override
  State<ConnectivitySettings> createState() => _ConnectivitySettingsState();
}

class _ConnectivitySettingsState extends State<ConnectivitySettings> {
  bool _wifi = true;
  bool _bluetooth = false;
  bool _airplane = false;

  Widget _row(IconData icon, String label, bool value, ValueChanged<bool>? onChanged, {Color? color}) => MergeSemantics(
    // One item for a screen reader: "Wi-Fi, switch, on".
    child: SizedBox(
      height: 52,
      child: Row(
        children: <Widget>[
          Icon(icon),
          const SizedBox(width: 12),
          Expanded(child: Text(label)),
          GlassSwitch(value: value, onChanged: onChanged, activeColor: color ?? const Color(0xFF34C759)),
        ],
      ),
    ),
  );

  @override
  Widget build(BuildContext context) => GlassCard(
    padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 4),
    child: Column(
      mainAxisSize: MainAxisSize.min,
      children: <Widget>[
        _row(
          Icons.flight,
          'Airplane mode',
          _airplane,
          (bool v) => setState(() => _airplane = v),
          color: const Color(0xFFFF9F0A),
        ),
        // While airplane mode is on, the radios can't be changed: onChanged null disables them.
        _row(Icons.wifi, 'Wi-Fi', _wifi, _airplane ? null : (bool v) => setState(() => _wifi = v)),
        _row(
          Icons.bluetooth,
          'Bluetooth',
          _bluetooth,
          _airplane ? null : (bool v) => setState(() => _bluetooth = v),
          color: const Color(0xFF0A84FF),
        ),
      ],
    ),
  );
}

API

Parameter Type Default Description
value bool required Whether the switch is on.
onChanged ValueChanged<bool>? required Called with the new value. Null disables the switch (drawn at 50% opacity).
activeColor Color Color(0xFF34C759) Track colour when on (iOS green).
trackColor Color Color(0x29787880) Track colour when off.
dropScale double kGlassDropScale Size of the held drop relative to the knob. At least 1.
dropWiden double kGlassSwitchDropWiden How many px of the surroundings the drop pulls in (a slight zoom-out). 0 for none.
semanticLabel String? null Screen-reader label. Or wrap the row in MergeSemantics with a Text.
key Key? null

Constants

Name Value Description
kGlassSwitchSize Size(64, 28) The track. The widget is at least 44 px tall.
kGlassDropScale 1.57 Held drop size relative to the knob (switch and slider).
kGlassSwitchDropWiden 5 The switch's default dropWiden, in px.
kGlassDropOptics GlassOptics(thickness: 10, strength: -4.1) The drop's refraction.
kGlassDropDuration Duration(milliseconds: 180) How long the drop takes to grow in and out.
kGlassDisabledOpacity 0.5 Opacity of a disabled switch or slider.