Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
3 changes: 3 additions & 0 deletions .agents/skills/add-new-component/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ Key rules:
- `displayName` set for React DevTools (e.g., `'Component.Trigger'`)
- `cx()` from `class-variance-authority` to merge CSS module class with user's `className`
- Spread `...props` last so consumers can override defaults
- **`data-slot` on every rendered element** — a stable, kebab-case, component-prefixed identifier (`data-slot='component-trigger'`, `data-slot='component-panel-label'`). Subparts extend the prefix; a repeated part reuses one name. Slot names are public API covered by semver — renaming one is a breaking change. Put the attribute before the `...props` spread so consumers can override. Skip `Popover.Content`/`Menu.Content` call sites (extra props land on the positioner, not the popup).

### Object.assign Composition

Expand Down Expand Up @@ -270,6 +271,7 @@ import styles from '../<name>.module.css';
4. **Keyboard navigation** — Tab, Enter, Space, Arrow keys as applicable
5. **Disabled state** — `aria-disabled`, no toggle on click
6. **Sub-components** — className, ref forwarding for each sub-component
7. **data-slot contract** — a `__tests__/data-slots.test.tsx` asserting every slot renders, using `expectSlots` from `~/test-utils/data-slots` (pass `document.body` for portaled parts; also assert conditional slots disappear when their part is absent)

### Testing Tips

Expand Down Expand Up @@ -497,6 +499,7 @@ Checklist:
- [ ] All tests pass
- [ ] Docs site builds and new page is generated
- [ ] `displayName` set on all sub-components
- [ ] Every rendered element has a `data-slot`, with a `data-slots.test.tsx` covering them
- [ ] CSS uses `--rs-*` tokens only
- [ ] Export in `packages/raystack/index.tsx` in alphabetical order
- [ ] Playground example added and registered
74 changes: 74 additions & 0 deletions apps/www/src/content/docs/(overview)/styling.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,80 @@ Apsara components built on [Base UI](https://base-ui.com/) expose data attribute

Common data attributes include `data-open`, `data-closed`, `data-active`, `data-disabled`, `data-state`, `data-starting-style`, and `data-ending-style`.

### With data-slot

Every part a component renders carries a `data-slot` attribute — a stable, public identifier for that element. Use it to style inner parts that have no `className` prop of their own, without depending on Apsara's internal (hashed) class names:

```css
/* Tint the remove button inside every filter chip */
[data-slot="filter-chip-remove"] {
color: var(--rs-color-foreground-danger-primary);
}

/* Make DataView loading rows taller */
[data-slot="data-view-list-loader-row"] {
min-height: 56px;
}

/* Hide the magnifier icon inside Search */
[data-slot="search"] [data-slot="search-input"] {
padding-left: var(--rs-space-3);
}
```

Slot names follow one convention:

- **kebab-case**, prefixed with the component name: `filter-chip-label`, `data-view-list-row`.
- Subparts extend the prefix: `data-view-filter-summary-clear`.
- The same part keeps the same name everywhere it appears (loader cells in `DataView.List` reuse `data-view-list-cell`).

Slot names are covered by semver — renaming or removing one is a breaking change, so selectors written against them are safe to keep. This is different from state attributes like `data-open` above: state attributes tell you *how* an element currently is, `data-slot` tells you *what* it is.

Combine them freely:

```css
/* Only style rows of a clickable list while it is loading */
[data-slot="data-view-list-loader-row"][aria-busy="true"] {
opacity: 0.6;
}
```

Slots also make reliable selectors for tests (`querySelector('[data-slot="search-clear-button"]')`) instead of reaching for hashed CSS-module classes.

#### Styling based on what a component contains

Because every part carries its own slot, a parent can change its own styling based on which children are present — no prop needed to signal it. CSS's `:has()` reaches into descendants:

```css
/* A filter bar that's tighter when it has no chips yet */
[data-slot="data-view-filters"]:has([data-slot="filter-chip"]) {
gap: var(--rs-space-4);
}

/* Style a submit button differently only inside a form's footer */
[data-slot="dialog-footer"] [data-slot="button"] {
width: 100%;
}
```

With Tailwind's arbitrary variants, the same thing reads as a utility class instead of a stylesheet rule:

```tsx
<Flex className="has-[[data-slot=filter-chip]]:gap-4">
<DataView.Filters />
</Flex>
```

```tsx
<Dialog.Footer className="[&_[data-slot=button]]:w-full">
<Button>Save</Button>
</Dialog.Footer>
```

This is the same technique as the state-based selectors above (`data-[state=open]:...`) — `data-slot` just gives you a target that's stable across releases instead of one tied to internal markup.

Coverage: every Apsara component exposes `data-slot` on every element it renders (`data-table` is the one exception — it's being phased out in favor of `DataView`). Each component's docs page lists its slot names.

## Theming with Data Attributes

The `Theme` component sets data attributes on the root `<html>` element. Use these to conditionally style elements based on the active theme:
Expand Down
15 changes: 15 additions & 0 deletions apps/www/src/content/docs/components/alert-dialog/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,21 @@ Renders the alert dialog footer section.

<auto-type-table path="./props.ts" name="AlertDialogFooterProps" />

### Slots

Every rendered part carries a stable `data-slot` attribute for [styling and testing](/docs/styling#with-data-slot):

| Slot | Element |
|------|---------|
| `alert-dialog-backdrop` | The overlay behind the alert dialog |
| `alert-dialog-viewport` | Viewport wrapper that positions the alert dialog |
| `alert-dialog-content` | The alert dialog popup |
| `alert-dialog-header` | The `AlertDialog.Header` container |
| `alert-dialog-title` | The `AlertDialog.Title` element |
| `alert-dialog-body` | The `AlertDialog.Body` container |
| `alert-dialog-description` | The `AlertDialog.Description` element |
| `alert-dialog-footer` | The `AlertDialog.Footer` row |

## Examples

### Controlled
Expand Down
8 changes: 8 additions & 0 deletions apps/www/src/content/docs/components/amount/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,14 @@ Formats and displays monetary values with locale and currency support.

<auto-type-table path="./props.ts" name="AmountProps" />

### Slots

Every rendered part carries a stable `data-slot` attribute for [styling and testing](/docs/styling#with-data-slot):

| Slot | Element |
|------|---------|
| `amount` | The root `<span>` element |

## Examples

### Basic
Expand Down
10 changes: 10 additions & 0 deletions apps/www/src/content/docs/components/badge/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,16 @@ Renders a small label for status, count, or category indication.

<auto-type-table path="./props.ts" name="BadgeProps" />

### Slots

Every rendered part carries a stable `data-slot` attribute for [styling and testing](/docs/styling#with-data-slot):

| Slot | Element |
|------|---------|
| `badge` | The root `<span>` element |
| `badge-icon` | Wrapper around `icon` (when set) |
| `badge-screen-reader-text` | Visually hidden text (when `screenReaderText` is set) |

## Examples

### Variant
Expand Down
19 changes: 19 additions & 0 deletions apps/www/src/content/docs/components/breadcrumb/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,25 @@ Renders a collapsed indicator for hidden breadcrumb items.

<auto-type-table path="./props.ts" name="BreadcrumbEllipsisProps" />

### Slots

Every rendered part carries a stable `data-slot` attribute for [styling and testing](/docs/styling#with-data-slot):

| Slot | Element |
|------|---------|
| `breadcrumb` | The root `<nav>` |
| `breadcrumb-list` | The `<ol>` wrapping items |
| `breadcrumb-item` | The `<li>` wrapping each item |
| `breadcrumb-link` | The link/span rendering the item's content |
| `breadcrumb-item-text` | The item's label text (when a leading/trailing icon is set) |
| `breadcrumb-leading-icon` | Leading icon (when `leadingIcon` is set) |
| `breadcrumb-trailing-icon` | Trailing icon (when `trailingIcon` is set) |
| `breadcrumb-dropdown-trigger` | Trigger button (when `dropdownItems` is set) |
| `breadcrumb-dropdown-icon` | Trigger chevron icon (when `dropdownItems` is set) |
| `breadcrumb-dropdown-item` | An entry in the dropdown menu |
| `breadcrumb-separator` | `Breadcrumb.Separator` |
| `breadcrumb-ellipsis` | `Breadcrumb.Ellipsis` |

## Examples

### Size
Expand Down
12 changes: 12 additions & 0 deletions apps/www/src/content/docs/components/button/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,18 @@ Renders a clickable button element.

<auto-type-table path="./props.ts" name="ButtonProps" />

### Slots

Every rendered part carries a stable `data-slot` attribute for [styling and testing](/docs/styling#with-data-slot):

| Slot | Element |
|------|---------|
| `button` | The root `<button>` element |
| `button-leading-icon` | Wrapper around `leadingIcon` (when set and not loading) |
| `button-trailing-icon` | Wrapper around `trailingIcon` (when set and not loading) |
| `button-loader` | The loading spinner (when `loading`) |
| `button-loader-text` | Wrapper around `loaderText` (when `loading` with `loaderText`) |

## Examples

### Variant
Expand Down
11 changes: 11 additions & 0 deletions apps/www/src/content/docs/components/checkbox/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,17 @@ Groups multiple checkboxes and manages their shared checked state via a `string[

<auto-type-table path="./props.ts" name="CheckboxGroupProps" />

### Slots

Every rendered part carries a stable `data-slot` attribute for [styling and testing](/docs/styling#with-data-slot):

| Slot | Element |
|------|---------|
| `checkbox` | The checkbox control itself |
| `checkbox-indicator` | The indicator inside the control |
| `checkbox-icon` | The check or indeterminate icon (when checked or indeterminate) |
| `checkbox-group` | The `Checkbox.Group` container |

## Examples

### States
Expand Down
12 changes: 12 additions & 0 deletions apps/www/src/content/docs/components/chip/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,18 @@ Renders a compact element for tags, labels, or attributes.

<auto-type-table path="./props.ts" name="ChipProps" />

### Slots

Every rendered part carries a stable `data-slot` attribute for [styling and testing](/docs/styling#with-data-slot):

| Slot | Element |
|------|---------|
| `chip` | The root element (`<button>` when interactive, `<span>` otherwise) |
| `chip-leading-icon` | Wrapper around `leadingIcon` (when set) |
| `chip-trailing-icon` | Wrapper around `trailingIcon` (when set and not dismissible) |
| `chip-dismiss` | The dismiss button (when `isDismissible`) |
| `chip-dismiss-icon` | The dismiss icon (when `isDismissible`) |

## Examples

### Variant
Expand Down
24 changes: 24 additions & 0 deletions apps/www/src/content/docs/components/command/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,30 @@ Renders the portal, viewport, and popup for the command palette. There is no bac

<auto-type-table path="./props.ts" name="CommandDialogContentProps" />

### Slots

Every rendered part carries a stable `data-slot` attribute for [styling and testing](/docs/styling#with-data-slot):

| Slot | Element |
|------|---------|
| `command` | The root panel |
| `command-input-container` | Wrapper around the search input |
| `command-input` | The `<input>` element itself |
| `command-content` | The `Command.Content` list |
| `command-empty` | `Command.Empty` |
| `command-group` | `Command.Group` wrapper |
| `command-label` | `Command.Label` |
| `command-item` | Each `Command.Item` |
| `command-item-leading-icon` | Leading icon inside an item (when provided) |
| `command-item-label` | The item's label text |
| `command-item-trailing-icon` | Trailing icon inside an item (when provided) |
| `command-separator` | `Command.Separator` |
| `command-shortcut` | `Command.Shortcut` wrapper |
| `command-shortcut-key` | Each `<kbd>` key inside a shortcut |
| `command-dialog-trigger` | `Command.DialogTrigger` |
| `command-dialog-viewport` | Viewport wrapper for the dialog popup |
| `command-dialog-content` | `Command.DialogContent` popup |

## Examples

### Inline
Expand Down
8 changes: 8 additions & 0 deletions apps/www/src/content/docs/components/container/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,14 @@ Renders a centered content wrapper with max-width constraints.

<auto-type-table path="./props.ts" name="ContainerProps" />

### Slots

Every rendered part carries a stable `data-slot` attribute for [styling and testing](/docs/styling#with-data-slot):

| Slot | Element |
|------|---------|
| `container` | The container `<div>` |

## Examples

### Size
Expand Down
20 changes: 20 additions & 0 deletions apps/www/src/content/docs/components/context-menu/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,26 @@ The content container for a submenu. Shares the same API as `ContextMenu.Content

<auto-type-table path="./props.ts" name="ContextMenuSubContentProps" />

### Slots

Every rendered part carries a stable `data-slot` attribute for [styling and testing](/docs/styling#with-data-slot):

| Slot | Element |
|------|---------|
| `context-menu-trigger` | The `ContextMenu.Trigger` element |
| `context-menu-positioner` | Positioning wrapper around the content popup |
| `context-menu-content` | The `ContextMenu.Content` / `ContextMenu.SubmenuContent` popup |
| `context-menu-search-input` | The autocomplete search input (when `autocomplete`) |
| `context-menu-search-list` | Wrapper around items in autocomplete mode (when `autocomplete`) |
| `context-menu-item` | Each `ContextMenu.Item` |
| `context-menu-group` | `ContextMenu.Group` wrapper |
| `context-menu-label` | `ContextMenu.Label` |
| `context-menu-separator` | `ContextMenu.Separator` |
| `context-menu-empty-state` | `ContextMenu.EmptyState` |
| `menu-subtrigger` | `ContextMenu.SubmenuTrigger` (shared with `Menu`'s internals) |
| `menu-cell-leading-icon` | Leading icon inside an item or submenu trigger (when provided; shared with `Menu`'s internals) |
| `menu-cell-trailing-icon` | Trailing icon inside an item or submenu trigger (when provided; shared with `Menu`'s internals) |

## Examples

### Basic Usage
Expand Down
12 changes: 12 additions & 0 deletions apps/www/src/content/docs/components/copy-button/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,18 @@ The CopyButton component extends IconButton props and accepts the following addi

All IconButton props are supported

### Slots

Every rendered part carries a stable `data-slot` attribute for [styling and testing](/docs/styling#with-data-slot):

| Slot | Element |
|------|---------|
| `copy-button` | The root `<button>` element |
| `copy-button-icons` | Wrapper that swaps between the copy and check icons |
| `copy-button-copy-icon` | The copy icon |
| `copy-button-check-icon` | The check icon shown after copying |
| `copy-button-status` | Visually hidden live region announcing "Copied" |

## Examples

### Size
Expand Down
Loading
Loading