<!-- Generated by scripts/gen-docs.mts from the JSDoc in registry/. Do not edit; run `pnpm run docs`. -->

# SwipeActions

A list row with actions beside it that a horizontal swipe reveals: swipe toward the start for the `trailing` actions (Delete, Archive), toward the end for the `leading` ones (Pin). Wrap the row, usually an `Item`, and name it with `label`. The swipe is for touch and pen, and it is never the only way: a long press on the row opens the same actions as a `Menu` at the finger, for anyone who cannot swipe, and the row's actions button opens that menu too. A mouse sees the button at the end of the row, and its right-click keeps the browser's own menu ("Open in new tab" for a row that is a link); on a touch screen the button is hidden from sight, and a keyboard or screen reader reaches it on any device. While a finger is down on the row, text selection and the iOS callout are off, so the long press opens the menu.

```tsx
import { SwipeActions } from "@/components/ouro/swipe-actions/swipe-actions";
```

## Example

```tsx
<ItemGroup label="Recipes">
  {recipes.map((recipe) => (
    <SwipeActions
      key={recipe.id}
      label={recipe.title}
      leading={[{ label: "Pin", tone: "primary", onClick: () => pin(recipe) }]}
      trailing={[
        {
          label: "Delete",
          tone: "destructive",
          onClick: () => {
            remove(recipe);
            toast("Recipe deleted", { action: { label: "Undo", onClick: () => restore(recipe) } });
          },
        },
        { label: "Archive", onClick: () => archive(recipe) },
      ]}
    >
      <Item href={`/recipes/${recipe.id}`} title={recipe.title} />
    </SwipeActions>
  ))}
</ItemGroup>
```

## Usage

The first action of each side is its primary one: it sits at the outer edge, and a swipe past 60% of the row runs it on release, with no confirmation. So make a destructive primary action undoable: delete, then offer Undo in a toast, as in the example.

The row follows the finger. A quick flick opens or closes it however short, by the direction it was moving at release; a slow release opens it past half the actions' width. A flick alone never runs an action: that takes the distance. The row springs to rest from where it was let go, and a new swipe can catch it on the way.

A vertical swipe that starts on a row scrolls the page. A row that is a link follows it on a tap, never on a swipe. One row is open at a time, and an open row closes on a tap outside it, on a tap on the row, when the page scrolls and on Escape. `leading` and `trailing` follow the reading direction. With reduced motion the row still follows the finger, so a swipe shows what it is about to do, and on release it jumps to rest instead of springing.

## Use instead when

- When it cannot be undone, open a `ConfirmDialog` from `onClick` instead.
- Use `Item` with `actions` instead when the buttons should always show, and `Menu` with `contextTrigger` for actions on something that is not a list row.

## Props

Type: `SwipeActionsProps`.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `actionsLabel` | `string` | `"Actions for {label}"` | The accessible name of the row's actions button; `{label}` is the row's `label`. Translate it with the rest of your interface. |
| `children` (required) | `ReactNode` |  | The row itself, usually an `Item`. It may be a link (`<Item href>`): a tap follows it and a swipe does not. |
| `className` | `string` |  | Layout only: margins (`mt-2`). Appearance classes warn in development; give an action a `tone`. |
| `label` (required) | `string` |  | Names the row for its actions button: "Braised short ribs" gives "Actions for Braised short ribs". It is always required. |
| `leading` | `readonly SwipeAction[]` |  | The actions a swipe toward the end reveals at the start edge, the first one outermost. A full swipe runs the first. |
| `trailing` | `readonly SwipeAction[]` |  | The actions a swipe toward the start reveals at the end edge, the first one outermost. A full swipe runs the first. |

## Don't use

These are not props. Passing one fails the type check with the fix in the error, and in development a console message names the fix.

- `actions`: Not a prop. Ouro UI names the side: `trailing={[{ label: "Delete", onClick: remove }]}`.
- `left`: Not a prop. Ouro UI uses `leading`, which follows the reading direction: `leading={[{ label: "Pin", onClick: pin }]}`.
- `leftActions`: Not a prop. Ouro UI uses `leading`: `leading={[{ label: "Pin", onClick: pin }]}`.
- `onDelete`: Not a prop. Deleting is an action: `trailing={[{ label: "Delete", tone: "destructive", onClick: remove }]}`.
- `right`: Not a prop. Ouro UI uses `trailing`, which follows the reading direction: `trailing={[{ label: "Delete", onClick: remove }]}`.
- `rightActions`: Not a prop. Ouro UI uses `trailing`: `trailing={[{ label: "Delete", onClick: remove }]}`.

## `className` is for layout only

Pass margins, width and grid placement (`mt-4`, `w-full`, `col-span-2`).

Appearance comes from props, not classes. In development, a class that sets appearance logs one warning per component instance, naming the prop to use instead. A class sets appearance when, after its variants (`hover:`, `sm:`) and any `!` are removed, it starts with `bg-`, `text-`, `border-`, `rounded-`, `shadow-`, `font-`, `ring-`, `from-`, `via-`, `to-`, `fill-` or `stroke-`, or is `border`, `rounded`, `shadow` or `ring`, or has a `dark:` variant. Text wrapping, overflow and table layout classes (`text-wrap`, `text-ellipsis`, `border-collapse`) are layout and do not warn.

Alignment classes (`text-center`) warn too: set alignment on a parent element.

## Types

### `SwipeAction`

One action of a `SwipeActions` row: a button beside the row, and an item in the row's actions menu.

```ts
interface SwipeAction {
  /** The button's text: a short verb, such as "Delete" or "Pin". */
  label: string;
  /** Runs when the action is chosen: by a tap on its button, from the actions menu, or by a full swipe when it is the first action of its side. The row then closes. */
  onClick: () => void;
  /**
   * The button's color, a theme token name: `destructive` for an action that deletes.
   * @default "secondary"
   */
  tone?: ToneName | (string & { 'danger is "destructive"; accent is "primary"; neutral is "secondary"': never }) | undefined;

  /** Not a field. Ouro UI uses `onClick`: `{ label: "Delete", onClick: remove }`. */
  onPress?: { 'Use onClick: { label: "Delete", onClick: remove }': never };
  /** Not a field. Ouro UI uses `tone`: `{ label: "Delete", tone: "destructive", onClick: remove }`. */
  variant?: { 'Use tone: { label: "Delete", tone: "destructive", onClick: remove }': never };
}
```
