# TaskCompletionControl

> The round check that leads a task row or card and marks the task Done in one press, or reopens a done one.

Source: https://raft-design.botiverse.build/components/task-completion-control. Generated from the current release; where it differs from what you remember, this page is right.

TaskCompletionControl is how a list of tasks is worked through as a to-do list: one press marks the task Done, and a press on a done task reopens it. It is a round check at the start of every task row and card, beside a conversation, under a message, on a board and in a list. When it shows and what a press does to the task are in [Completing a task](https://raft-design.botiverse.build/patterns/tasks.md#completing-a-task).

```tsx
import { useState } from "react";
import { TaskCompletionControl } from "@botiverse/raft-ui/wip";

export function TaskCompletionControlPreviewContent() {
  const [done, setDone] = useState(false);
  return (
    <div className="flex items-center gap-2.5">
      <TaskCompletionControl
        checked={done}
        onCheckedChange={setDone}
        aria-label="#25 Fix onboarding copy"
      />
      <span className="font-mono text-mono-meta text-foreground-hint">#25</span>
      <span
        className={
          done ? "text-callout text-foreground-hint line-through" : "text-callout text-foreground"
        }
      >
        Fix onboarding copy
      </span>
    </div>
  );
}
```

## When to use

| Situation                                                       | Use                                                                                          |
| --------------------------------------------------------------- | -------------------------------------------------------------------------------------------- |
| Completing or reopening a task, at the start of its row or card | TaskCompletionControl                                                                        |
| Selecting a row, to act on several at once                      | [Checkbox](https://raft-design.botiverse.build/components/checkbox.md), or `CheckboxIndicator` in a row that owns the semantics |
| Changing a task to any other status                             | The status chip's menu ([TaskCard](https://raft-design.botiverse.build/components/task-card.md))                               |
| A yes or no that is saved with a form                           | [Checkbox](https://raft-design.botiverse.build/components/checkbox.md)                                                          |

## Anatomy

- `TaskCompletionControl`: a native `button` with checkbox semantics, holding a round `CheckboxIndicator` (`shape="circle"`), 20px. Required on every task row and card. The row or card around it, the title it names and its tooltip are the caller's.

## States

```tsx
import { useState } from "react";
import { TaskCompletionControl } from "@botiverse/raft-ui/wip";

export function TaskCompletionControlStatesContent() {
  const [first, setFirst] = useState(false);
  const [second, setSecond] = useState(true);
  return (
    <div className="flex items-center gap-6">
      <TaskCompletionControl
        checked={first}
        onCheckedChange={setFirst}
        aria-label="#25 Fix onboarding copy"
      />
      <TaskCompletionControl
        checked={second}
        onCheckedChange={setSecond}
        aria-label="#26 Write the release notes"
      />
      <TaskCompletionControl checked={false} disabled aria-label="#27 Update the pricing page" />
      <TaskCompletionControl checked disabled aria-label="#28 Archive the old roadmap" />
    </div>
  );
}
```

- **Unchecked:** an empty circle with an ink edge, on every status but Done: Backlog and Closed included.
- **Hover:** while unchecked, a faint check shows when the pointer is over the control itself. A hover elsewhere on the row is a hover on the row and previews nothing, even when the row or a column around it is a Tailwind `group`.
- **Checked:** an ink circle with the check knocked out, on a Done task only.
- **Disabled:** for a person who can't change the task: the disabled look, `aria-disabled` and no preview. It doesn't set the native `disabled`, which would take the button out of the keyboard's reach: focus moving through a list of tasks couldn't stop on it, a screen reader would never hear that it is locked, and its tooltip couldn't open. It keeps its focus, and a press or Space does nothing.

## Behavior

- Pass `checked` from the task's status and change the task in `onCheckedChange`: it is called with `true` to complete the task and `false` to reopen it. The control keeps no state of its own; what completing does (the Toast, the pause before the task moves to Done, Undo) is the caller's ([Completing a task](https://raft-design.botiverse.build/patterns/tasks.md#completing-a-task)).
- A press stops at the control: its click, Space and Enter never reach the row or card it sits on, so it never also opens the task, and on a [TaskBoard](https://raft-design.botiverse.build/components/task-board.md) card Space completes the task instead of picking the card up. Arrow keys still reach the list, for moving between rows.
- An `onClick` that calls `event.preventDefault()` keeps `onCheckedChange` from being called.
- Its hit area reaches 8px past the 20px circle with a mouse (36px) and 12px on a touch screen (44px) ([Accessibility](https://raft-design.botiverse.build/foundations/accessibility.md#hit-targets)). With a mouse it reaches less far sideways than a Checkbox's 12px, which would cover the task number beside it.

## Content

- Name it by the task: its number, then its title, "#25 Fix onboarding copy".
- Put the action in its tooltip if the product shows one: Complete, Reopen on a done one ([Tooltip](https://raft-design.botiverse.build/components/tooltip.md)).

## Accessibility

TaskCompletionControl renders a native `button` with `role="checkbox"` and wraps no Base UI primitive. `aria-checked` is true only on a done task. It isn't Base UI's Checkbox because it needs none of that checkbox's hidden input (a task's state isn't a form value), Enter must never toggle it, and a click must stay on this button rather than reach the row or card under it.

- Give it `aria-label` (or `aria-labelledby`) with the task; the type requires one. Keep the same name when the state changes: the checked state already says whether the task is done, so a name that says what a press does ("Reopen #25") would be read as "Reopen #25, checkbox, checked".
- Space toggles it. Enter doesn't, as on any checkbox, so it isn't taken for the row's Enter, which opens the task.
- It shows its own focus ring, apart from the row's.
- `CheckboxIndicator` inside it is `aria-hidden`; the button carries the role, the state and the name.

## Rules

- Keep it round. A square box at the start of a row reads as "select this row", which is what a Checkbox there means; the circle reads as "done", as in a to-do list. Round is otherwise only for switches, radios and grab handles; this control is one of the two exceptions, with the Android pull-to-refresh disc ([Shape](https://raft-design.botiverse.build/foundations/shape.md#principles)).
- Use it only for completing a task. Don't use a round `CheckboxIndicator` or a round Checkbox for anything else ([Checkbox](https://raft-design.botiverse.build/components/checkbox.md#rules)).
- Show it checked only on a Done task. A press on any other status, Backlog and Closed included, completes the task.
- Make it the first thing on a task row or card, and its own button on top of the row or card: pressing the rest still opens the task.

## API

### TaskCompletionControl

The round control that marks a task Done in one press, or reopens a done one. Controlled: pass `checked` from the task's status and change the task in `onCheckedChange`.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `aria-label` | `string` |  | The task, by number and title: "#25 Fix onboarding copy". It stays the same when the task is completed or reopened. |
| `aria-labelledby` | `string` |  | The id of the element that names the task, such as its title. |
| `checked` (required) | `boolean` |  | Whether the task is done. Only a Done task is checked; every other status, Backlog and Closed included, is unchecked. |
| `disabled` | `boolean` | `false` | For a person who can't change the task. Sets `aria-disabled`, not the native `disabled`: the control stays focusable and a press does nothing. |
| `onCheckedChange` | `(checked: boolean, event: MouseEvent<HTMLButtonElement>) => void` |  | Called with the state a press asks for: `true` to complete the task, `false` to reopen it. The caller changes the task. |

Also takes the attributes of its element.
