PixelDosa UI

Human Handoff

When a person takes over from the agent — and what they're handed.

Handover

Waiting for a person to take over. Waiting 6 minutes.

The customer asked for a refund outside the policy window. I can't decide this one.

Customer
Priya Menon · acme.com · 3 years, no refunds before
Order
#48120 · £240 · delivered 31 days ago
They asked
A full refund; the product stopped working after 4 weeks.
I already tried
Offered a replacement and a 30% credit. Both declined.
Policy
Refunds within 30 days. This is day 31.

Installation

bash
npx shadcn@latest add @pixeldosa/human-handoff

Requires `cn` at `@/lib/utils`. Fill `context` with the facts a colleague would ask for in the first thirty seconds, including what the agent already tried.

Usage

"use client";

import { HumanHandoff } from "@/components/ui/human-handoff";

<HumanHandoff
  stage="waitingForPerson"
  reason="The customer asked for a refund outside the policy window. I can't decide this one."
  context={[
    { id: "customer", label: "Customer", value: "Priya Menon · 3 years, no refunds before" },
    { id: "tried", label: "I already tried", value: "Offered a replacement and a 30% credit. Both declined." },
  ]}
  onTakeOver={take}
/>

Why it exists

Handoffs usually lose everything: the customer repeats themselves, whoever takes over re-reads a transcript to find three facts, and nobody records what the agent already tried — so the same rejected remedy gets offered twice.

Features

  • The context is the component. The facts a colleague would ask for in the first thirty seconds, including what the agent already tried.
  • The reason is in the agent's own words, so the person knows why it stopped.
  • It works both ways. Handing a finished job back to the agent is a real stage, not an afterthought.
  • Waiting holds completely still, in the waiting colour, the same treatment approvals get.

Variants

Waiting for a person

Refund decision

Waiting for a person to take over. Waiting 6 minutes.

The customer asked for a refund outside the policy window. I can't decide this one.

Order
#48120 · £240 · delivered 31 days ago
I already tried
Offered a replacement and a 30% credit. Both declined.

A person has it

Billing question

A person has taken over. Dana in support.

Dana has the conversation. I'll keep the notes up to date.

Props

PropTypeNotes
stage"agentHandling" | "waitingForPerson" | "personHandling" | "returnedToAgent"Required.
reasonstringWhy the agent stepped back, in its words.
context{ id, label, value }[]The facts the person receives.
personstringWho has it now.
waitingForstringHow long, formatted by you.
onTakeOver / takeOverLabel() => void / string
onReturnToAgent / returnLabel() => void / string
titlestringDefaults to "Handover".
headingLevel2 | 3 | 4 | 5 | 6Defaults to 3.

Accessibility

  • A labelled <section>; the context is a description list, so labels and values are paired for screen readers.
  • The stage is stated in words, not only by colour.
  • Nothing animates in any stage.

When not to use

  • One consequential action awaiting a decision: use AI Approval Gate.
  • A question the agent could answer itself: use Agent Ask.
  • Routing or assignment rules: that's your queueing system, not this.

why it's built this way

Engineering Notes

Stage is a four-value enum rather than a boolean because the return leg is a real state with its own copy, and products that model handoff as 'escalated: true' have nowhere to put the moment a person finishes and the agent resumes. Waiting is styled in the waiting colour with full stillness, the same treatment approvals get, so a queue of handoffs reads at a glance without a separate legend. The context list is `dl` markup rather than free text so a caller can render five facts consistently and a screen reader hears label-and-value pairs, and it sits inside the component rather than being left to a slot because the whole failure mode is teams shipping a handoff button with no payload.

how it moves, and when it doesn't

Motion Notes

Nothing animates, in any stage. A waiting handoff is a person's turn and the strongest signal available is complete stillness; a pulsing 'needs attention' badge here would compete with the queue around it and train people to ignore it.