Pull request card
A pull request an agent opened, with its merge box: status, branches, author, change totals with the five-cell bar, checks with failures first, review state, and a Merge button gated the way the host gates it, with the reason in words when it is blocked.
On this page
Installation
Usage
import { PullRequestCard } from "@/components/ui/chatcn/pull-request-card"
<PullRequestCard
title="Paginate the runs list"
number={482}
status="open"
repo="brightwell/console"
branch="feat/runs-pagination"
baseBranch="main"
author={{ name: "Fern", kind: "agent" }}
files={files}
checks={checks}
review={{ state: "approved", reviewers: ["Ines Duarte"] }}
href={pr.html_url}
merging={merging}
onMerge={merge}
/>The card takes plain props, so it works with any Git host. Map your API response once: files takes the same shape as ChangedFiles (a DiffFile from chatcn-diff fits), checks is a list of check runs, and review is the review decision with reviewer names. Without a file list, pass stats for the totals.
Merge is gated the way hosts gate it: the pull request must be open and not a draft, without conflicts, with every required check passed and an approving review. Checks marked required: false never block. When the gate is closed, Merge stays disabled and the merge row says why in one sentence, linked to the button with aria-describedby. Pass canMerge when your host already decided, and blockedReason to explain branch protection in your own words.
// Your host knows best: let it decide, and say why when it says no.
<PullRequestCard
{...props}
canMerge={pr.mergeable_state === "clean"}
blockedReason="Merging needs a review from a code owner of src/lib."
mergeError={error ? "main moved while merging. Update the branch, then retry." : undefined}
/>Examples
Failing checks and blocked merges
Failing checks sort to the top and open the list by themselves. Each check shows its result line, its duration once finished, and a Details link. Merge stays disabled with the reason: failing required checks, requested changes or conflicts with the base branch.
States
Draft offers Ready for review instead of Merge. Merged and closed settle into one line with who did it. A failed merge says how to fix it and offers Retry merge. loading draws a static skeleton in the card's final shape.
Compact rows
variant="compact" is one 40 px line for chat answers and lists: status glyph, title, number, check counts with glyphs and the +/- counts. With href, the whole row is the link.
Status colors
| Status | Glyph and color | Word |
|---|---|---|
open | Pull request glyph, green | Open |
draft | Pull request glyph, gray | Draft |
merged | Merge glyph, purple | Merged |
closed | Pull request glyph, red | Closed |
Checks use the kit's status vocabulary: a green check for passed, a red cross for failed, a blue spinner for running, a dashed gray circle for queued and a gray minus for skipped. Every color has a glyph and a word next to it.
Props
| Prop | Type | Notes |
|---|---|---|
title, number | ReactNode, number | Required |
status | "open" | "draft" | "merged" | "closed" | Required |
repo | string | owner/name |
branch, baseBranch | string | Head and base. baseBranch defaults to main |
author | { name, kind?: "agent" | "person", avatar? } | Agents get a quiet Agent label. avatar is a 16 px slot |
meta | ReactNode | Extra text in the meta line, for example opened 6 min ago |
files | ChangedFile[] | Totals, the five-cell bar and an expandable file list |
stats | { files, additions, deletions } | Totals without a file list |
checks | PullRequestCheck[] | { name, status, description?, durationMs?, href?, required? } |
review | { state, reviewers? } | "approved" | "changes-requested" | "review-required" | "commented" |
mergeable | "ready" | "conflicts" | "blocked" | "unknown" | What the host reports about the merge |
canMerge | boolean | Overrides the gate. Conflicts and drafts still block |
blockedReason | ReactNode | Shown for mergeable="blocked" or canMerge={false} |
merging | boolean | The merge is in flight |
mergeError | ReactNode | Why the last attempt failed. Shows Retry merge |
mergeLabel | string | Defaults to Merge. For example Squash and merge |
mergedBy | string | Who merged or closed it |
onMerge | () => void | Shows the Merge button on open pull requests |
onReadyForReview | () => void | Shows Ready for review on drafts |
onFileSelect | (path) => void | A file in the expanded list was chosen |
href | string | Open pull request and Copy link |
checksOpen, defaultChecksOpen, onChecksOpenChange | boolean | Opens by itself when a check fails |
loading | boolean | Static skeleton |
variant | "card" | "plain" | "compact" | plain sits inside your surface |
actions | ReactNode | Extra controls at the start of the footer |
mergeGate, sortChecks and summarizeChecks are exported too, for when you want the same rules elsewhere, such as in a notification.
Keyboard
| Key | Action |
|---|---|
Tab | Copy link, files, checks, Details links, then the footer actions |
Enter, Space | Expand or collapse the files or checks list |
ArrowDown, ArrowUp | Move through the expanded file list |
Accessibility
- Status is always a word next to the glyph. Colors back it up and never carry it alone.
- A disabled Merge button points at the sentence that explains it with
aria-describedby. - Each check reads its name, result and duration, and Details links are named, for example Details for ci / lint.
- Merged, closed and a failed merge are announced once through a polite live region.
- The compact row starts with hidden text such as Open pull request 482, so a list of rows reads well.
- Rows and buttons grow to 44 px on touch screens. Branches are mono and the arrow between them flips in right to left layouts.
Motion
- The status glyph and each row glyph swap with the kit's 150 ms blur crossfade, so open to merging to merged reads as one object changing.
- The files and checks lists open with a 200 ms height and opacity transition; the chevron turns in the same 200 ms.
- Merging is the only shimmer, on its one status label. Under reduced motion the crossfades and height transitions are dropped and the spinner slows down.