Skip to content
Sign in
CodePull request cardMenu

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.

Required checks green, approved, six files with the five-cell bar. Merge runs, then the card settles as merged.

Installation

$ pnpm dlx shadcn@latest add https://chatcn.dev/r/pull-request-card.json

Usage

tsx
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.

tsx
// 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.

Failing checks open by themselves and sort first. Merge stays disabled and says why: failing checks, requested changes or conflicts.

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.

Draft with Ready for review, merged, closed, a failed merge with retry and the loading skeleton.

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.

One line per pull request for chat answers: status glyph, title, number, check counts and +/- counts.

Status colors

StatusGlyph and colorWord
openPull request glyph, greenOpen
draftPull request glyph, grayDraft
mergedMerge glyph, purpleMerged
closedPull request glyph, redClosed

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

PropTypeNotes
title, numberReactNode, numberRequired
status"open" | "draft" | "merged" | "closed"Required
repostringowner/name
branch, baseBranchstringHead and base. baseBranch defaults to main
author{ name, kind?: "agent" | "person", avatar? }Agents get a quiet Agent label. avatar is a 16 px slot
metaReactNodeExtra text in the meta line, for example opened 6 min ago
filesChangedFile[]Totals, the five-cell bar and an expandable file list
stats{ files, additions, deletions }Totals without a file list
checksPullRequestCheck[]{ 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
canMergebooleanOverrides the gate. Conflicts and drafts still block
blockedReasonReactNodeShown for mergeable="blocked" or canMerge={false}
mergingbooleanThe merge is in flight
mergeErrorReactNodeWhy the last attempt failed. Shows Retry merge
mergeLabelstringDefaults to Merge. For example Squash and merge
mergedBystringWho merged or closed it
onMerge() => voidShows the Merge button on open pull requests
onReadyForReview() => voidShows Ready for review on drafts
onFileSelect(path) => voidA file in the expanded list was chosen
hrefstringOpen pull request and Copy link
checksOpen, defaultChecksOpen, onChecksOpenChangebooleanOpens by itself when a check fails
loadingbooleanStatic skeleton
variant"card" | "plain" | "compact"plain sits inside your surface
actionsReactNodeExtra 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

KeyAction
TabCopy link, files, checks, Details links, then the footer actions
Enter, SpaceExpand or collapse the files or checks list
ArrowDown, ArrowUpMove 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.