# Widget
> A Widget is a container that structures content behind a titled header with its own actions, most often on a dashboard.

## Import

```jsx
import { Widget } from '@volue/wave-react';
```

Widget is a compound component made up of several parts:

- `Widget.Root`: The wrapper that contains all the parts of a widget.
- `Widget.Header`: The header strip holding the title and the actions.
- `Widget.Title`: The title. Renders an `h3` by default.
- `Widget.Subtitle`: Optional supporting text below the title.
- `Widget.Actions`: The container for header actions. Moves actions that no longer fit into an overflow popover.
- `Widget.Select`: An action that opens a selection.
- `Widget.SelectClear`: A button beside a select that clears or resets its selection.
- `Widget.Button`: A labelled action.
- `Widget.IconButton`: A square, icon-only action.
- `Widget.Body`: The wrapper containing the widget's main content.

## When to use

Widgets encapsulate a feature that stands on its own, in a styled container with a title and controls of its own. Dashboard panels are the typical case. They are responsive: as a widget narrows, its header collapses the actions into an overflow popover. A [Card](https://wave.volue.com/components/card.md) belongs a level below: it groups related content, even inside a widget itself.

## Examples

### Basic

> **Tip**
>
> Every widget is exposed to assistive technology as a region landmark named by
> its title, so screen reader users can jump between the widgets of a dashboard.

```jsx resizable=true
<Widget.Root>
  <Widget.Header>
    <Widget.Title>{'Title'}</Widget.Title>
  </Widget.Header>
  <Widget.Body>
    <Flex flow="column">
      <FakeText words="30" />
      <FakeText words="30" />
    </Flex>
  </Widget.Body>
</Widget.Root>
```

### Visual variants

The `variant` prop picks how the widget separates from its surroundings, either outlined by a border or lifted by an elevation.

```jsx resizable=true
<Box padding="spacingL" css={{ backgroundColor: '$backgroundNeutralSubtle' }}>
  <Flex flow="column" gap="spacingL">
    <Widget.Root variant="bordered">
      <Widget.Header>
        <Widget.Title>{'Bordered (default)'}</Widget.Title>
      </Widget.Header>
      <Widget.Body>
        <ExampleBox striped css={{ minHeight: '8rem' }}>
          {'Content'}
        </ExampleBox>
      </Widget.Body>
    </Widget.Root>
    <Widget.Root variant="elevated">
      <Widget.Header>
        <Widget.Title>{'Elevated'}</Widget.Title>
      </Widget.Header>
      <Widget.Body>
        <ExampleBox striped css={{ minHeight: '8rem' }}>
          {'Content'}
        </ExampleBox>
      </Widget.Body>
    </Widget.Root>
  </Flex>
</Box>
```

### With a subtitle

Add `Widget.Subtitle` alongside `Widget.Title`, and the header stacks them for you.

```jsx resizable=true
<Widget.Root>
  <Widget.Header>
    <Widget.Title>{'Title'}</Widget.Title>
    <Widget.Subtitle>{'Updated 2 minutes ago'}</Widget.Subtitle>
  </Widget.Header>
  <Widget.Body>
    <ExampleBox striped css={{ minHeight: '8rem' }}>
      {'Content'}
    </ExampleBox>
  </Widget.Body>
</Widget.Root>
```

### Sizes

Widget comes in `medium` and `small` sizes.

```jsx resizable=true
() => {
  const defaultDate = new Date(2026, 1, 26);
  const [date, setDate] = React.useState(defaultDate);

  const formatDate = date =>
    date.toLocaleDateString('en-GB', {
      day: '2-digit',
      month: '2-digit',
      year: '2-digit'
    });

  const sizes = [
    { size: 'medium', title: 'Medium (default)', contentHeight: '8rem' },
    { size: 'small', title: 'Small', contentHeight: '6rem' }
  ];

  return (
    <Flex flow="column" gap="spacingL">
      {sizes.map(({ size, title, contentHeight }) => (
        <Widget.Root key={size} size={size}>
          <Widget.Header>
            <Widget.Title>{title}</Widget.Title>
            <Widget.Actions>
              <DatePicker.Root
                size={size}
                value={date}
                onValueChange={nextDate => setDate(nextDate ?? defaultDate)}
              >
                <DatePicker.CalendarTrigger
                  as={Widget.Select}
                  label="Period"
                  value={formatDate(date)}
                />
                <DatePicker.Calendar />
              </DatePicker.Root>
              <Widget.IconButton
                aria-label="Reset period"
                onClick={() => setDate(defaultDate)}
              >
                <SvgIcon iconName="reload" />
              </Widget.IconButton>
            </Widget.Actions>
          </Widget.Header>
          <Widget.Body>
            <ExampleBox striped css={{ minHeight: contentHeight }}>
              {'Content'}
            </ExampleBox>
          </Widget.Body>
        </Widget.Root>
      ))}
    </Flex>
  );
};
```

### Vertical scroll

`Widget.Body` scrolls on its own within whatever height the surrounding layout
gives the widget.

```jsx resizable=true
<Box css={{ height: '20rem' }}>
  <Widget.Root>
    <Widget.Header>
      <Widget.Title>{'Title'}</Widget.Title>
      <Widget.Subtitle>
        {'Scroll the body and the header stays'}
      </Widget.Subtitle>
    </Widget.Header>
    <Widget.Body>
      <FakeText words="280" />
    </Widget.Body>
  </Widget.Root>
</Box>
```

### Clearable select

Put a `Widget.SelectClear` in a select's `endElement` slot to let users clear it
in one click. The button shows only while the select has a `value`.

```jsx resizable=true
() => {
  const [filter, setFilter] = React.useState('Option 1');

  return (
    <Widget.Root>
      <Widget.Header>
        <Widget.Title>{'Title'}</Widget.Title>
        <Widget.Actions>
          <SingleSelectMenu.Root value={filter} onValueChange={setFilter}>
            <SingleSelectMenu.CustomTrigger
              as={Widget.Select}
              label="Filter"
              placeholder="All"
              value={filter}
              endElement={<Widget.SelectClear onClick={() => setFilter('')} />}
            />
            <SingleSelectMenu.Content>
              {['Option 1', 'Option 2', 'Option 3'].map(option => (
                <SingleSelectMenu.Item key={option} value={option}>
                  {option}
                </SingleSelectMenu.Item>
              ))}
            </SingleSelectMenu.Content>
          </SingleSelectMenu.Root>
        </Widget.Actions>
      </Widget.Header>
      <Widget.Body>
        <ExampleBox striped css={{ minHeight: '8rem' }}>
          {'Content'}
        </ExampleBox>
      </Widget.Body>
    </Widget.Root>
  );
};
```

### Actions

`Widget.Actions` holds the header's controls. There are three kinds of action:

- `Widget.Select` is a labelled action that shows the current `value`. Use it for choosing something.
- `Widget.Button` is a labelled action, usually with a `leftIcon`.
- `Widget.IconButton` is an icon-only action.

None of them has any behaviour of its own. Each one is a trigger, and you choose
what it opens. Compose an action with a `SingleSelectMenu.CustomTrigger`, a
`Combobox.CustomTrigger`, a `Popover.Trigger`, a `Dialog.Trigger` or any other
trigger component, and the action _becomes_ that trigger.

```jsx
<SingleSelectMenu.Root value={period} onValueChange={setPeriod}>
  <SingleSelectMenu.CustomTrigger
    as={Widget.Select}
    label="Period"
    value={period}
  />
  <SingleSelectMenu.Content>{/* the choices */}</SingleSelectMenu.Content>
</SingleSelectMenu.Root>
```

`Widget.Actions` is responsive. When the actions no longer fit the widget's
width, the leading ones move into an overflow popover and stack there as outline
[Buttons](https://wave.volue.com/components/button.md). Collapsing changes nothing about what an action
does. It still opens the same menu, popover or dialog as it did in the header.

> **Tip**
>
> Drag the preview's edge to watch the actions collapse one by one, and open one
> from the overflow popover to see it still working as the same trigger.

> **Tip**
>
> In this example the body sets `padding="none"` so the
> [Table](https://wave.volue.com/components/tables/table.md) runs flush to the widget's edges. Do the same
> for charts and other edge-to-edge content.

```jsx resizable=true
() => {
  const contracts = [
    ['NO2 base', 'NO2', new Date(2026, 1, 26), 120, 41.15, 1.8],
    ['NO2 peak', 'NO2', new Date(2026, 1, 26), 80, 52.8, -0.6],
    ['FI base', 'FI', new Date(2026, 1, 27), 140, 46.3, 0.7],
    ['SE3 base', 'SE3', new Date(2026, 1, 28), 65, 38.9, 0.4],
    ['DK1 base', 'DK1', new Date(2026, 2, 1), 95, 44.6, -1.4],
    ['DK2 peak', 'DK2', new Date(2026, 2, 2), 55, 51.2, 2.1],
    ['NO1 base', 'NO1', new Date(2026, 2, 4), 110, 40.05, 1.1],
    ['NO5 base', 'NO5', new Date(2026, 2, 6), 90, 39.75, -0.3],
    ['SE4 base', 'SE4', new Date(2026, 2, 10), 75, 48.6, 2.4],
    ['SE1 base', 'SE1', new Date(2026, 2, 12), 85, 35.2, -0.9],
    ['NO3 peak', 'NO3', new Date(2026, 2, 16), 70, 42.35, -1.7],
    ['SE2 peak', 'SE2', new Date(2026, 2, 20), 60, 45.8, 1.5],
    ['DK2 base', 'DK2', new Date(2026, 2, 24), 105, 47.1, 0.9],
    ['FI peak', 'FI', new Date(2026, 2, 27), 50, 55.4, -2.2],
    ['NO2 quarterly', 'NO2', new Date(2026, 2, 31), 160, 43.9, -1.1],
    ['SE3 quarterly', 'SE3', new Date(2026, 2, 31), 130, 41.7, 0.8],
    ['DK1 quarterly', 'DK1', new Date(2026, 2, 31), 100, 45.95, 1.3],
    ['NO1 quarterly', 'NO1', new Date(2026, 3, 1), 115, 42.5, -0.4],
    ['FI quarterly', 'FI', new Date(2026, 3, 1), 145, 47.8, 0.6]
  ].map(([name, priceArea, delivery, volume, price, change]) => ({
    name,
    priceArea,
    delivery,
    volume,
    price,
    change
  }));

  const firstDelivery = contracts[0].delivery;
  const lastDelivery = contracts[contracts.length - 1].delivery;
  const priceAreas = [...new Set(contracts.map(row => row.priceArea))].sort();

  const defaults = {
    priceAreas: [],
    change: '',
    contract: '',
    delivery: firstDelivery
  };

  const [selectedPriceAreas, setSelectedPriceAreas] = React.useState(
    defaults.priceAreas
  );
  const [changeFilter, setChangeFilter] = React.useState(defaults.change);
  const [contract, setContract] = React.useState(defaults.contract);
  const [delivery, setDelivery] = React.useState(defaults.delivery);
  const [columns, setColumns] = React.useState({
    'Price area': true,
    Delivery: true,
    Volume: true,
    Price: true,
    Change: true
  });

  const matchesChange = row =>
    changeFilter === '' ||
    (changeFilter === 'Rising' ? row.change > 0 : row.change < 0);

  const rows = contracts.filter(
    row =>
      matchesChange(row) &&
      (selectedPriceAreas.length === 0 ||
        selectedPriceAreas.includes(row.priceArea)) &&
      row.name.toLowerCase().includes(contract.toLowerCase()) &&
      row.delivery >= delivery
  );

  const formatDate = date =>
    date.toLocaleDateString('en-GB', {
      day: '2-digit',
      month: '2-digit',
      year: '2-digit'
    });

  const formatPrice = value =>
    value.toLocaleString('en-GB', { style: 'currency', currency: 'EUR' });

  const formatChange = value => `${value > 0 ? '+' : ''}${value.toFixed(1)}%`;

  const areaFlags = { DK: '🇩🇰', FI: '🇫🇮', NO: '🇳🇴', SE: '🇸🇪' };
  const areaFlag = area => areaFlags[area.slice(0, 2)];

  const resetFilters = () => {
    setSelectedPriceAreas(defaults.priceAreas);
    setChangeFilter(defaults.change);
    setContract(defaults.contract);
    setDelivery(defaults.delivery);
  };

  const hasCustomFilters =
    selectedPriceAreas.length > 0 ||
    changeFilter !== defaults.change ||
    contract !== defaults.contract ||
    delivery.getTime() !== defaults.delivery.getTime();

  return (
    <Widget.Root size="small">
      <Widget.Header>
        <Widget.Title>{'Portfolio'}</Widget.Title>
        <Widget.Actions>
          <Popover.Root>
            <Popover.Trigger
              as={Widget.Select}
              label="Contract"
              placeholder="All contracts"
              value={contract}
              endElement={
                <Widget.SelectClear
                  onClick={() => setContract(defaults.contract)}
                />
              }
            />
            <Popover.Content size="small" css={{ minWidth: '15rem' }}>
              <Flex
                as="form"
                flow="column"
                onSubmit={event => {
                  event.preventDefault();
                  setContract(
                    new FormData(event.currentTarget).get('contract')
                  );
                }}
              >
                <FormField.Root as={Flex} flow="column" size="small">
                  <FormField.Label>{'Contract name'}</FormField.Label>
                  <TextInput
                    name="contract"
                    defaultValue={contract}
                    placeholder="e.g. NO2 base"
                  />
                  <FormField.HelperText>
                    {'Matches anywhere in the name.'}
                  </FormField.HelperText>
                </FormField.Root>
                <Flex main="end">
                  <Popover.Close as={Button} size="small" type="submit">
                    {'Apply'}
                  </Popover.Close>
                </Flex>
              </Flex>
            </Popover.Content>
          </Popover.Root>

          {/* Select — a searchable multi-select */}
          <Combobox.Root
            multiple
            items={priceAreas}
            value={selectedPriceAreas}
            onValueChange={setSelectedPriceAreas}
            size="small"
          >
            <Combobox.CustomTrigger
              as={Widget.Select}
              label="Price area"
              placeholder="All areas"
              value={
                selectedPriceAreas.length > 2
                  ? `${selectedPriceAreas.length} price areas`
                  : selectedPriceAreas.join(', ')
              }
              endElement={
                <Widget.SelectClear
                  onClick={() => setSelectedPriceAreas(defaults.priceAreas)}
                />
              }
            />
            <Combobox.Content>
              <Combobox.TextInput
                aria-label="Search price areas"
                placeholder="Search price areas"
              />
              <Combobox.List>
                {item => (
                  <Combobox.Item
                    key={item}
                    value={item}
                    startElement={<span>{areaFlag(item)}</span>}
                    endElement={<Combobox.ItemIndicator />}
                  >
                    {item}
                  </Combobox.Item>
                )}
              </Combobox.List>
              <Combobox.NoValueFound>
                {'No price areas found'}
              </Combobox.NoValueFound>
            </Combobox.Content>
          </Combobox.Root>

          {/* Select — a list of choices */}
          <SingleSelectMenu.Root
            value={changeFilter}
            onValueChange={setChangeFilter}
          >
            <SingleSelectMenu.CustomTrigger
              as={Widget.Select}
              label="Change"
              placeholder="Both"
              value={changeFilter}
              endElement={
                <Widget.SelectClear
                  onClick={() => setChangeFilter(defaults.change)}
                />
              }
            />
            <SingleSelectMenu.Content size="small">
              {['Rising', 'Falling'].map(option => (
                <SingleSelectMenu.Item key={option} value={option}>
                  {option}
                </SingleSelectMenu.Item>
              ))}
            </SingleSelectMenu.Content>
          </SingleSelectMenu.Root>

          {/* Select — a date, filtering the Delivery column */}
          <DatePicker.Root
            value={delivery}
            onValueChange={nextDate => setDelivery(nextDate ?? firstDelivery)}
            minDate={firstDelivery}
            maxDate={lastDelivery}
            size="small"
          >
            <DatePicker.CalendarTrigger
              as={Widget.Select}
              label="Delivery"
              value={`from ${formatDate(delivery)}`}
            />
            <DatePicker.Calendar />
          </DatePicker.Root>

          {hasCustomFilters && (
            <Widget.Button
              leftIcon={<SvgIcon iconName="undo" />}
              onClick={resetFilters}
            >
              {'Reset'}
            </Widget.Button>
          )}

          {/* IconButton — icon only in the header, named by its aria-label */}
          <Dialog.Root size="small">
            <Dialog.Trigger as={Widget.IconButton} aria-label="Column settings">
              <SvgIcon iconName="settings" />
            </Dialog.Trigger>
            <Dialog.Box width="22rem">
              <Dialog.Header>
                <Dialog.Title>{'Column settings'}</Dialog.Title>
                <Dialog.Close
                  as={Button}
                  variant="ghost"
                  shape="circle"
                  withLoneIcon
                  aria-label="Close"
                  marginLeft="auto"
                >
                  <SvgIcon iconName="close" />
                </Dialog.Close>
              </Dialog.Header>
              <Dialog.Body>
                <Flex flow="column" gap="spacingS">
                  {Object.entries(columns).map(([column, isVisible]) => (
                    <Checkbox.Root
                      key={column}
                      isChecked={isVisible}
                      onChange={event =>
                        setColumns({
                          ...columns,
                          [column]: event.target.checked
                        })
                      }
                    >
                      <Checkbox.Indicator />
                      <Checkbox.Label>{column}</Checkbox.Label>
                    </Checkbox.Root>
                  ))}
                </Flex>
              </Dialog.Body>
              <Dialog.Footer>
                <Flex main="end">
                  <Dialog.Close as={Button}>{'Save'}</Dialog.Close>
                </Flex>
              </Dialog.Footer>
            </Dialog.Box>
          </Dialog.Root>
        </Widget.Actions>
      </Widget.Header>
      <Widget.Body
        padding="none"
        css={{ maxHeight: '18rem', contain: 'inline-size' }}
      >
        <Table.Root
          aria-label="Contracts"
          cellSize="small"
          outlined={false}
          addHorizontalWhitespace
        >
          <Table.Header stickyOffset={0}>
            <Table.ColumnHeader minWidth={100}>{'Contract'}</Table.ColumnHeader>
            {columns['Price area'] && (
              <Table.ColumnHeader minWidth={80}>
                {'Price area'}
              </Table.ColumnHeader>
            )}
            {columns.Delivery && (
              <Table.ColumnHeader minWidth={90}>
                {'Delivery'}
              </Table.ColumnHeader>
            )}
            {columns.Volume && (
              <Table.ColumnHeader align="right" minWidth={80}>
                {'Volume'}
              </Table.ColumnHeader>
            )}
            {columns.Price && (
              <Table.ColumnHeader align="right" minWidth={70}>
                {'Price'}
              </Table.ColumnHeader>
            )}
            {columns.Change && (
              <Table.ColumnHeader align="right" minWidth={90}>
                {'Change'}
              </Table.ColumnHeader>
            )}
          </Table.Header>
          {rows.length > 0 ? (
            <Table.Body>
              {rows.map(row => (
                <Table.Row key={row.name}>
                  <Table.Cell minWidth={100}>{row.name}</Table.Cell>
                  {columns['Price area'] && (
                    <Table.Cell minWidth={80}>
                      <Flex gap="spacingXs">
                        <span>{areaFlag(row.priceArea)}</span>
                        {row.priceArea}
                      </Flex>
                    </Table.Cell>
                  )}
                  {columns.Delivery && (
                    <Table.Cell minWidth={90}>
                      {formatDate(row.delivery)}
                    </Table.Cell>
                  )}
                  {columns.Volume && (
                    <Table.Cell
                      align="right"
                      minWidth={80}
                    >{`${row.volume} MW`}</Table.Cell>
                  )}
                  {columns.Price && (
                    <Table.Cell align="right" minWidth={70}>
                      {formatPrice(row.price)}
                    </Table.Cell>
                  )}
                  {columns.Change && (
                    <Table.Cell
                      align="right"
                      minWidth={90}
                      tone={row.change < 0 ? 'danger' : 'success'}
                    >
                      {formatChange(row.change)}
                    </Table.Cell>
                  )}
                </Table.Row>
              ))}
            </Table.Body>
          ) : (
            <Table.EmptyState>
              <Text as="p" variant="zeta" color="foregroundNeutralSubtle">
                {'No contracts match the current filters.'}
              </Text>
            </Table.EmptyState>
          )}
        </Table.Root>
      </Widget.Body>
    </Widget.Root>
  );
};
```

<Card
  href="https://next--63ac6ba300bd3c91074bc891.chromatic.com/?path=/story/components-widget--wired-actions"
  title="See complete example in Storybook"
  actionIcon="externalLink"
/>

## API reference

### Widget.Root

| Name      | Type                                                      | Default      | Description                                                                                                                                                                                                                                                                                                           | Required |
| --------- | --------------------------------------------------------- | ------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- |
| `as`      | `keyof JSX.IntrinsicElements \| React.ComponentType<any>` | `div`        | Change the component to a different HTML tag or custom component. This will merge the original component props with the props of the supplied element/component and change the underlying DOM node.   For more details, read our [Composition](https://wave.volue.com/get-started/composition.md#polymorphism) guide. |          |
| `css`     | `StitchesCss`                                             |              | Apply styles directly to a component in a similar way how you would define inline styles. Wave uses [Stitches](https://stitches.dev/) under the hood with a fully-typed API and support for features like tokens, media queries or variants.                                                                          |          |
| `variant` | `"bordered" \| "elevated"`                                | `"bordered"` | Whether the widget is outlined by a border or lifted by an elevation.                                                                                                                                                                                                                                                 |          |
| `size`    | `"small" \| "medium"`                                     | `"medium"`   | The size of the header, its type and its actions, on the same scale as [Dialog](https://wave.volue.com/components/dialog.md). Also the control size a collapsed action renders at.                                                                                                                                    |          |

### Widget.Header

| Name  | Type                                                      | Default | Description                                                       | Required |
| ----- | --------------------------------------------------------- | ------- | ----------------------------------------------------------------- | -------- |
| `as`  | `keyof JSX.IntrinsicElements \| React.ComponentType<any>` | `div`   | Change the component to a different HTML tag or custom component. |          |
| `css` | `StitchesCss`                                             |         | Apply styles directly to a component.                             |          |

### Widget.Title

| Name  | Type                                                      | Default | Description                                                                                                               | Required |
| ----- | --------------------------------------------------------- | ------- | ------------------------------------------------------------------------------------------------------------------------- | -------- |
| `as`  | `keyof JSX.IntrinsicElements \| React.ComponentType<any>` | `h3`    | Change the component to a different HTML tag or custom component. Use it to fit the title into your page's heading order. |          |
| `css` | `StitchesCss`                                             |         | Apply styles directly to a component.                                                                                     |          |

### Widget.Subtitle

| Name  | Type                                                      | Default | Description                                                       | Required |
| ----- | --------------------------------------------------------- | ------- | ----------------------------------------------------------------- | -------- |
| `as`  | `keyof JSX.IntrinsicElements \| React.ComponentType<any>` | `span`  | Change the component to a different HTML tag or custom component. |          |
| `css` | `StitchesCss`                                             |         | Apply styles directly to a component.                             |          |

### Widget.Actions

| Name            | Type          | Default          | Description                                                       | Required |
| --------------- | ------------- | ---------------- | ----------------------------------------------------------------- | -------- |
| `overflowLabel` | `string`      | `"More actions"` | Accessible label for the trigger that opens the overflow popover. |          |
| `css`           | `StitchesCss` |                  | Apply styles directly to a component.                             |          |

### Widget.Select

| Name          | Type                                                      | Default  | Description                                                                                                                                                                                                                                    | Required |
| ------------- | --------------------------------------------------------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- |
| `as`          | `keyof JSX.IntrinsicElements \| React.ComponentType<any>` | `button` | Change the component to a different HTML tag or custom component. The usual way to put something behind the action is to compose the other way round, passing `as={Widget.Select}` to a `SingleSelectMenu.CustomTrigger` or `Popover.Trigger`. |          |
| `css`         | `StitchesCss`                                             |          | Apply styles directly to a component.                                                                                                                                                                                                          |          |
| `label`       | `React.ReactNode`                                         |          | What the action selects. Shown on its own while nothing is selected, and above the `value` once something is. Collapsed, it lifts out of the control and sits above it.                                                                        |          |
| `value`       | `React.ReactNode`                                         |          | The current selection. Leave empty (`''`, `null` or `false`) while nothing is selected.                                                                                                                                                        |          |
| `placeholder` | `React.ReactNode`                                         |          | Stands in for the value while nothing is selected. In the header, a labelled select shows only its label instead; the placeholder shows when the select has no label or moves into the overflow popover.                                       |          |
| `endElement`  | `React.ReactElement`                                      |          | `ReactElement` to render beside the trigger, sized for an icon-only button. Intended for `Widget.SelectClear`.                                                                                                                                 |          |
| `isActive`    | `boolean`                                                 | `false`  | Marks the action as active. Only needed for actions that are not triggers: a `SingleSelectMenu.CustomTrigger` or `Popover.Trigger` sets `aria-expanded`, which the action reads on its own.                                                    |          |
| `isDisabled`  | `boolean`                                                 | `false`  | Whether the action is disabled.                                                                                                                                                                                                                |          |

### Widget.SelectClear

| Name      | Type                                         | Default   | Description                                                                                                                 | Required |
| --------- | -------------------------------------------- | --------- | --------------------------------------------------------------------------------------------------------------------------- | -------- |
| `css`     | `StitchesCss`                                |           | Apply styles directly to a component.                                                                                       |          |
| `label`   | `string`                                     | `"Clear"` | The accessible name of the button. Screen readers hear it followed by the select's `label`, for example “Clear Price area”. |          |
| `onClick` | `React.MouseEventHandler<HTMLButtonElement>` |           | Clears or resets the selection. Focus moves to the select first, so the button can safely unmount as a result.              | Yes      |

### Widget.Button

| Name         | Type                                                      | Default  | Description                                                                                      | Required |
| ------------ | --------------------------------------------------------- | -------- | ------------------------------------------------------------------------------------------------ | -------- |
| `as`         | `keyof JSX.IntrinsicElements \| React.ComponentType<any>` | `button` | Change the component to a different HTML tag or custom component.                                |          |
| `css`        | `StitchesCss`                                             |          | Apply styles directly to a component.                                                            |          |
| `leftIcon`   | `React.ReactElement`                                      |          | An icon before the label, typically an [SvgIcon](https://wave.volue.com/components/svg-icon.md). |          |
| `isActive`   | `boolean`                                                 | `false`  | Marks the action as active.                                                                      |          |
| `isDisabled` | `boolean`                                                 | `false`  | Whether the action is disabled.                                                                  |          |

### Widget.IconButton

| Name         | Type                                                      | Default  | Description                                                                                                                                                                            | Required |
| ------------ | --------------------------------------------------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- |
| `as`         | `keyof JSX.IntrinsicElements \| React.ComponentType<any>` | `button` | Change the component to a different HTML tag or custom component.                                                                                                                      |          |
| `css`        | `StitchesCss`                                             |          | Apply styles directly to a component.                                                                                                                                                  |          |
| `aria-label` | `string`                                                  |          | Names the action, which has no visible text of its own. It doubles as the visible label once the action collapses into the overflow popover, so write it as a label a user would read. | Yes      |
| `isActive`   | `boolean`                                                 | `false`  | Marks the action as active.                                                                                                                                                            |          |
| `isDisabled` | `boolean`                                                 | `false`  | Whether the action is disabled.                                                                                                                                                        |          |

### Widget.Body

> **Tip**
>
> In addition to the props below, you can pass the padding [Box props](https://wave.volue.com/components/layout/box.md#api-reference) to control the spacing.

| Name  | Type                                                      | Default | Description                                                       | Required |
| ----- | --------------------------------------------------------- | ------- | ----------------------------------------------------------------- | -------- |
| `as`  | `keyof JSX.IntrinsicElements \| React.ComponentType<any>` | `div`   | Change the component to a different HTML tag or custom component. |          |
| `css` | `StitchesCss`                                             |         | Apply styles directly to a component.                             |          |
