# Slab

> Slab is a headless Web Component library, published as `slab-elements`. Each element handles the hard parts — keyboard and focus, ARIA, form participation, events — on top of the native element it maps to. What it looks like is up to you: style it from the outside with `::part()` and `:state()`, or subclass it and make it your own.

## Why headless

- **Bring your own design.** Slab doesn't ship a single line of visual CSS. No theme to fight, nothing to override. Every piece you'd want to style is a `::part()`, and every state you'd react to is a `:state()`.

- **The platform first.** A Slab dialog is a `<dialog>`. A disclosure is a `<details>`. A switch is a checkbox. Slab leans on what browsers already do well and only fills in what's missing. Attributes in, events out.

- **Built to last.** Custom elements are a web standard, not a framework feature, so they work in React, Vue, Svelte, Rails, or plain HTML — and in whatever you're using three years from now.

## One element, any look

Every Slab element ships with no visual CSS, so the same `<slab-switch>` can be a plain checkbox, a hard-edged brick, a rounded pill, or anything else, with nothing but CSS from the outside. For example, a rounded pill:

```css
slab-switch::part(input) {
  appearance: none;
  background: #767676;
  block-size: 1.7rem;
  border-radius: 999px;
  inline-size: 3rem;
}

slab-switch:state(checked)::part(input) {
  background: #2563eb;
}
```

That's what headless means: Slab handles how it works, and you decide how it looks.

## Usage

```sh
pnpm add slab-elements lit
```

`lit` is a peer dependency. Import each element you use, which registers it,
with any bundler, or an import map, resolving the imports:

```html
<script type="module">
  import 'slab-elements/disclosure.js';
</script>

<slab-disclosure label="Foundation">
  Poured once, built on forever.
</slab-disclosure>
```

Nothing's styled until you style it. Every piece you'd want to reach is a
`::part()`, and every state you'd want to react to is a `:state()`:

```css
slab-disclosure::part(summary) {
  font-weight: 600;
}

slab-disclosure[open]::part(summary) {
  color: #b3381a;
}
```

Want to go further? Subclass an element, set `styles`, and register it under
a tag of your own. Its functional styles stick around underneath yours:

```js
import Disclosure from 'slab-elements/disclosure.js';
import { css } from 'lit';

customElements.define(
  'acme-disclosure',
  class extends Disclosure {
    static styles = css`
      [part~='summary'] {
        font-weight: 600;
      }
    `;
  },
);
```

## 32 elements

Every one has its own page. [Components](components.md) lists them all, grouped.

- **Overlays:** [Alert](components/alert.md), [Dialog](components/dialog.md), [Drawer](components/drawer.md), [Popover](components/popover.md), [Toast](components/toast.md), [Tooltip](components/tooltip.md)

- **Disclosure:** [Accordion](components/accordion.md), [Disclosure](components/disclosure.md)

- **Navigation:** [Breadcrumb](components/breadcrumb.md), [Menu](components/menu.md), [Pagination](components/pagination.md), [Tabs](components/tabs.md), [Toolbar](components/toolbar.md), [Tree](components/tree.md)

- **Selection:** [Combobox](components/combobox.md), [Listbox](components/listbox.md), [Select](components/select.md)

- **Form:** [Checkbox](components/checkbox.md), [Checkbox Group](components/checkbox-group.md), [Date Field](components/date-field.md), [Field](components/field.md), [File Drop](components/file-drop.md), [Form](components/form.md), [Number](components/number.md), [Radio Group](components/radio-group.md), [Slider](components/slider.md), [Switch](components/switch.md), [Text Field](components/text-field.md), [Textarea](components/textarea.md)

- **Data:** [Calendar](components/calendar.md), [Meter](components/meter.md), [Progress](components/progress.md)
