# Toggle button

A button that stays on or off, such as bold text, a map layer or a filter: the outline or ghost button when off, sunken into the page when on.

Block: `smbk-toggle-button`. Status: experimental. Figma: the Figma file, https://www.figma.com/community/file/1688324308358870630. Desktop and mobile: differ by presentation only.

## Options

| Axis | Values | Default | Meaning |
|---|---|---|---|
| `tone` | `outline`, `ghost` | `outline` | How it looks when off: the outline button, or the ghost button with no fill or edge, for a tool among others in a toolbar. On looks the same in both. Figma draws the outline toggle; the ghost one is the toolbar’s tool. |
| `size` | `lg`, `md`, `sm` | `md` | The button’s own heights, padding, label and icon, one recipe per height. md fits apps and forms. |
| `icon-only` | on / off | off | A square toggle holding one icon and no label, for a toolbar. Needs an accessible name. |

Parts: `smbk-btn__icon`, `smbk-btn__label`

## Markup

```html
<button
  type="button"
  aria-pressed="false"
  class="smbk-toggle-button smbk-btn smbk-btn--tone-outline smbk-btn--size-md"
>
  <span class="smbk-btn__label">Show archived</span>
</button>
```

## React

```tsx
import { ToggleButton } from './design-system/components/toggle-button/toggle-button.js';

<ToggleButton tone="outline" size="md">Show archived</ToggleButton>
```

## Stylesheet

```css
/* smbk-toggle-button — Toggle button. Figma "Toggle button" 7125:4263: On × State × Size, 24 variants.
 * The toggle button is the button with aria-pressed, and the button's stylesheet draws it: off is the outline or
 * ghost button exactly, and on (the sunken fill, the rim and the strong inset shadow) is in button/button.css, under
 * "on". Its markup carries the button's classes, its mark smbk-toggle-button and aria-pressed.
 * This file holds no rules: the toggle button's own class is only its mark in markup. */
```

## Tokens it reads

```json
{
  "space": {
    "inline": {
      "md": {
        "$type": "dimension",
        "$value": "{{space.8}}",
        "$description": "Gap between things side by side, medium."
      },
      "sm": {
        "$type": "dimension",
        "$value": "{{space.6}}",
        "$description": "Gap between things side by side, small."
      },
      "xs": {
        "$type": "dimension",
        "$value": "{{space.4}}",
        "$description": "Gap between things side by side, extra small."
      }
    },
    "inset": {
      "xl": {
        "$type": "dimension",
        "$value": "{{space.16}}",
        "$description": "Padding inside a component, extra large."
      },
      "2xl": {
        "$type": "dimension",
        "$value": "{{space.24}}",
        "$description": "Padding inside a component, 2× large."
      },
      "lg": {
        "$type": "dimension",
        "$value": "{{space.12}}",
        "$description": "Padding inside a component, large."
      },
      "md": {
        "$type": "dimension",
        "$value": "{{space.8}}",
        "$description": "Padding inside a component, medium."
      }
    }
  },
  "size": {
    "control": {
      "md": {
        "$type": "dimension",
        "$value": "{{size.40}}",
        "$description": "Height of a medium control, where controls insert by default: 40 comfortable, 32 compact. Dashboard recipe: use compact density; 40 for the page’s one main action and for forms; 32 for toolbars and filters, the size used most; 28 for actions inside table rows and cards. On phones switch to comfortable density, or give small controls an invisible 44 px tap area (size/target/comfortable)."
      },
      "lg": {
        "$type": "dimension",
        "$value": "{{size.48}}",
        "$description": "Height of a large control: 48 comfortable, 40 compact. A page may use it for its main call to action; apps use md. Dashboard recipe: use compact density; 40 for the page’s one main action and for forms; 32 for toolbars and filters, the size used most; 28 for actions inside table rows and cards. On phones switch to comfortable density, or give small controls an invisible 44 px tap area (size/target/comfortable)."
      },
      "sm": {
        "$type": "dimension",
        "$value": "{{size.32}}",
        "$description": "Height of a small control: 32 comfortable, 28 compact. Dashboard recipe: use compact density; 40 for the page’s one main action and for forms; 32 for toolbars and filters, the size used most; 28 for actions inside table rows and cards. On phones switch to comfortable density, or give small controls an invisible 44 px tap area (size/target/comfortable)."
      },
      "xs": {
        "$type": "dimension",
        "$value": "{{size.24}}",
        "$description": "Height of the extra-small control: 24 in both densities, the smallest target allowed. Dashboard recipe: use compact density; 40 for the page’s one main action and for forms; 32 for toolbars and filters, the size used most; 28 for actions inside table rows and cards. On phones switch to comfortable density, or give small controls an invisible 44 px tap area (size/target/comfortable)."
      }
    },
    "icon": {
      "md": {
        "$type": "dimension",
        "$value": "{{size.20}}",
        "$description": "Icon box, medium."
      },
      "sm": {
        "$type": "dimension",
        "$value": "{{size.16}}",
        "$description": "Icon box, small."
      },
      "xs": {
        "$type": "dimension",
        "$value": "{{size.12}}",
        "$description": "Icon box, extra small."
      }
    },
    "target": {
      "comfortable": {
        "$type": "dimension",
        "$value": "{{size.44}}",
        "$description": "Comfortable touch target (44)."
      }
    }
  },
  "border-width": {
    "hairline": {
      "$type": "dimension",
      "$value": "{{border-width.1}}",
      "$description": "Thin lines: dividers and almost every edge."
    }
  },
  "radius": {
    "control": {
      "sm": {
        "$type": "dimension",
        "$value": "{{radius.4}}",
        "$description": "Corners of controls, small."
      },
      "md": {
        "$type": "dimension",
        "$value": "{{radius.6}}",
        "$description": "Corners of controls, medium."
      }
    }
  },
  "color": {
    "action": {
      "brand": {
        "background": {
          "$type": "color",
          "$value": "{{color.red.600}}",
          "$description": "Brand button and chip: fill at rest."
        },
        "border": {
          "$type": "color",
          "$value": "{{color.alpha.white.10}}",
          "$description": "Brand button and chip: edge at rest."
        },
        "foreground": {
          "$type": "color",
          "$value": "{{color.grey.50}}",
          "$description": "Brand button and chip: label and icon at rest."
        },
        "background-hover": {
          "$type": "color",
          "$value": "{{color.red.700}}",
          "$description": "Brand button and chip: fill on hover."
        },
        "border-hover": {
          "$type": "color",
          "$value": "{{color.alpha.black.5}}",
          "$description": "Brand button and chip: edge on hover."
        },
        "background-pressed": {
          "$type": "color",
          "$value": "{{color.red.800}}",
          "$description": "Brand button and chip: fill while pressed, one step past hover."
        }
      },
      "disabled": {
        "foreground": {
          "$type": "color",
          "$value": "{{color.grey.500}}",
          "$description": "The label and icon of every disabled action, shared by every kind of button."
        },
        "background": {
          "$type": "color",
          "$value": "{{color.alpha.grey-800.8}}",
          "$description": "Background of a disabled button, in every tone: a faint see-through grey, always fainter than an enabled button. Not for tracks: a disabled switch or slider part uses color/surface/track-subtle."
        },
        "border": {
          "$type": "color",
          "$value": "{{color.grey.300}}",
          "$description": "The edge of every disabled action, shared by every kind of button."
        }
      },
      "secondary": {
        "background": {
          "$type": "color",
          "$value": "{{color.alpha.grey-800.15}}",
          "$description": "Secondary button: fill at rest."
        },
        "foreground": {
          "$type": "color",
          "$value": "{{color.grey.850}}",
          "$description": "Secondary button: label and icon at rest."
        },
        "background-hover": {
          "$type": "color",
          "$value": "{{color.alpha.grey-800.20}}",
          "$description": "Secondary button: fill on hover."
        },
        "background-pressed": {
          "$type": "color",
          "$value": "{{color.alpha.grey-800.25}}",
          "$description": "Secondary button: fill while pressed, one step past hover."
        }
      },
      "outline": {
        "border": {
          "$type": "color",
          "$value": "{{color.grey.300}}",
          "$description": "Outline button: edge at rest."
        },
        "foreground": {
          "$type": "color",
          "$value": "{{color.grey.850}}",
          "$description": "Outline button: label and icon at rest."
        },
        "background-hover": {
          "$type": "color",
          "$value": "{{color.alpha.grey-800.10}}",
          "$description": "Outline button: fill on hover."
        },
        "border-hover": {
          "$type": "color",
          "$value": "{{color.grey.400}}",
          "$description": "Outline button: edge on hover."
        },
        "background-pressed": {
          "$type": "color",
          "$value": "{{color.grey.300}}",
          "$description": "Outline button: fill while pressed, one step past hover."
        },
        "border-pressed": {
          "$type": "color",
          "$value": "{{color.grey.500}}",
          "$description": "Outline button: edge while pressed."
        }
      },
      "ghost": {
        "foreground": {
          "$type": "color",
          "$value": "{{color.grey.850}}",
          "$description": "Ghost button: label and icon at rest."
        },
        "background-hover": {
          "$type": "color",
          "$value": "{{color.alpha.grey-800.15}}",
          "$description": "Ghost button: fill on hover."
        },
        "background-pressed": {
          "$type": "color",
          "$value": "{{color.action.outline.background-pressed}}",
          "$description": "Ghost button: fill while pressed, one step past hover; also the current page in pagination."
        }
      },
      "danger": {
        "background": {
          "$type": "color",
          "$value": "{{color.red.300}}",
          "$description": "Danger button and menu item: fill at rest."
        },
        "border": {
          "$type": "color",
          "$value": "{{color.alpha.black.5}}",
          "$description": "Danger button and menu item: edge at rest."
        },
        "foreground": {
          "$type": "color",
          "$value": "{{color.red.800}}",
          "$description": "Danger button and menu item: label and icon at rest."
        },
        "background-hover": {
          "$type": "color",
          "$value": "{{color.red.400}}",
          "$description": "Danger button and menu item: fill on hover."
        },
        "border-hover": {
          "$type": "color",
          "$value": "{{color.alpha.red.30}}",
          "$description": "Danger button and menu item: edge on hover."
        },
        "foreground-hover": {
          "$type": "color",
          "$value": "{{color.red.950}}",
          "$description": "Danger button and menu item: label and icon on hover and while pressed."
        },
        "background-pressed": {
          "$type": "color",
          "$value": "{{color.red.500}}",
          "$description": "Danger button and menu item: fill while pressed, one step past hover."
        }
      }
    },
    "border": {
      "focus": {
        "$type": "color",
        "$value": "{{color.red.600}}",
        "$description": "Keyboard focus ring on controls."
      },
      "rim": {
        "$type": "color",
        "$value": "{{color.alpha.white.90}}",
        "$description": "The light rim that makes a surface look lit: fields, chosen pills, cards."
      }
    },
    "surface": {
      "sunken": {
        "$type": "color",
        "$value": "{{color.alpha.grey-800.5}}",
        "$description": "Recessed wells: fields, tracks, code. See-through, so a well always shows one step below what it sits on: the page, a card or a panel."
      }
    }
  },
  "typography": {
    "label-lg": {
      "$type": "typography",
      "$value": {
        "fontFamily": "Geist, Geist Fallback, system-ui, sans-serif",
        "fontSize": "16px",
        "fontWeight": 500,
        "lineHeight": 1.4,
        "letterSpacing": "0px"
      },
      "$description": "Label LG: labels on controls and short UI text (buttons, chips, tabs, menu headings). Geist Medium, sentence case. Code and keys use Code SM and Code MD."
    },
    "label-md": {
      "$type": "typography",
      "$value": {
        "fontFamily": "Geist, Geist Fallback, system-ui, sans-serif",
        "fontSize": "14px",
        "fontWeight": 500,
        "lineHeight": 1.4,
        "letterSpacing": "0px"
      },
      "$description": "Label MD: labels on controls and short UI text (buttons, chips, tabs, menu headings). Geist Medium, sentence case. Code and keys use Code SM and Code MD."
    }
  },
  "focus": {
    "ring": {
      "width": {
        "$type": "dimension",
        "$value": "{{border-width.2}}",
        "$description": "Thickness of the focus ring."
      },
      "offset": {
        "$type": "dimension",
        "$value": "{{space.2}}",
        "$description": "Gap between a control and its focus ring."
      }
    }
  },
  "elevation": {
    "inset": {
      "$type": "shadow",
      "$value": {
        "light": "inset 0px 0px 1px 0px #373d434d",
        "dark": "inset 0px 0px 1px 0px #00000066"
      }
    },
    "inset-strong": {
      "$type": "shadow",
      "$value": {
        "light": "inset -1px 2px 2px 0px #373d4333",
        "dark": "inset -1px 2px 2px 0px #0000004d"
      }
    }
  }
}
```

## Purpose

A button that stays on or off: bold text in an editor, a layer on a map, a filter that is applied. Pressing it switches a setting rather than running a one-off action, and it shows which way the setting is. It differs from the switch, which sets an option in a form or a settings list, and from the segmented control, which picks one of several choices.

## Anatomy

- The block, `smbk-toggle-button`, on a [button](button.md) with the outline button's look when off, or the ghost button's for a tool among others in a toolbar. The markup has the toggle's block, then the button's own classes, at the chosen tone and size. The button's stylesheet draws it, off and on.
- The label part and the icon part, the button's own: words, an icon before them, or an icon alone.

Being on is the button's pressed state, read from its markup; there is no class for it, so what the reader sees and what a screen reader hears always agree.

## When to use

- A setting that applies at once and stays applied until pressed again: bold, a grid overlay, a map layer, "Show archived".
- A tool in a toolbar that is either on or off.

## When not to use

- An option in a form or a settings list that is saved with the form. Use the [switch](switch.md), or a [checkbox](checkbox.md).
- One choice out of several. Use the [segmented control](segmented-control.md) or [radios](radio.md).
- A one-off action. Use the [button](button.md).

## Behavior

Off, it is the outline button, or the ghost button in the ghost tone, with the same hover and pressed looks. On, it sinks into the page: the sunken fill, a soft shadow along its inner top edge and a light rim for its edge. It keeps the same size on and off, so nothing around it moves. Hovering an on toggle keeps the on look. A disabled toggle takes the shared disabled look, and an on one keeps its rim.

Pressing it switches it. The wrapper holds its own state, or follows the state the page gives it and reports each press. Nothing animates.

## Accessibility
### What it does for you

- It is a real button that says whether it is pressed, so a screen reader reads "pressed" or "not pressed" after its name.
- Space and Enter press it. It is one stop in the tab order.
- On is never told by color alone: the fill, the shadow and the edge all change.

### What you must do

- Keep its name the same on and off: "Bold", not "Make bold" and "Remove bold". The pressed state says which it is.
- Give an icon-only toggle an accessible name. Without one the wrapper warns in development and names it by its icon's words, which say what it shows, not what it turns on.
- In markup written by hand, give it the button's classes and the toggle's mark, set the pressed attribute to true or false, and switch it on each press.

### How to test

- Tab to it: the focus ring shows, in both themes and in a forced-color mode. Press Space, then Enter: each switches it.
- With a screen reader, it is read as a button by its name, with "pressed" or "not pressed", and its name does not change when it is switched.
- Check that on and off differ by the fill, the shadow and the edge, not by color alone.

## Usage rules

### Do

- Name it for the setting it switches, the same in both states.
- Group related toggles together, such as bold, italic and underline.

### Don't

- Change its label when it is pressed. The pressed state says it.
- Use it for a choice saved with a form. Use the switch or a checkbox.
