Document Match

Pro

Order, receipt and invoice aligned line by line, so you read each line straight across and only the variances are tinted.

0exceptions0within tolerance0missing
0 to review
LineOrderPO-2026-0418ReceiptALB-7731InvoiceFV-26/1184Suministros Ebro S.L.Proposed action
Nitrile gloves, size M, box of 100GN-100-MMatched€272.0040 x €6.8040 unitsReceived 14 Oct€272.0040 x €6.80
Duct tape, grey, 50 mCA-50Allowed −10% to +2%, this line +1.9%Within tolerance€99.6024 x €4.1524 unitsReceived 14 Oct€101.5224 x €4.23+€1.92
Recycled euro palletPE-1200Allowed ±0%, this line +7.1%Exception€285.0030 x €9.5028 unitsReceived 14 Oct€285.0030 x €9.50+2 units
Stretch film, 23 micronFE-23Allowed −10% to +2%, this line +5%Exception€226.8012 x €18.9012 unitsReceived 14 Oct€238.2012 x €19.85+€11.40
Freight and packingNot on the orderMissingNot on this documentNot received€35.001 x €35.00
Thermal labels 100 x 150ET-100150Missing€120.0010 x €12.0010 unitsReceived 14 OctNot invoiced
Net€1,003.40Quantities only€931.72
VAT 21%€210.71€195.66
Total€1,214.11€1,127.38

Installation#

# One-time setup (tokens + lib helpers)npx koalaui-cli@latest init# Activate your Pro license (once per machine)npx koalaui-cli@latest login <key># Add this component (its dependencies come along)npx koalaui-cli@latest add document-match

Usage#

The grid matches nothing itself. Run your matching engine, then map each result line to a row: the status of the line, a cell per document, and the cell that disagrees marked with its own status and delta.

This example is part of Koala UI Pro

The preview is yours to explore. Its code comes with a Pro license, paid once and used in every project.

Have a license? Unlock it here

Anatomy#

import {  DocumentMatch,  DocumentMatchSummary,  DocumentMatchCount,  DocumentMatchNavigator,  DocumentMatchGrid,  DocumentMatchHeader,  DocumentMatchLineHead,  DocumentMatchDocument,  DocumentMatchDocumentTitle,  DocumentMatchDocumentNumber,  DocumentMatchDocumentMeta,  DocumentMatchActionHead,  DocumentMatchBody,  DocumentMatchLine,  DocumentMatchLineHeader,  DocumentMatchLineTitle,  DocumentMatchLineMeta,  DocumentMatchTolerance,  DocumentMatchCell,  DocumentMatchValue,  DocumentMatchValueDetail,  DocumentMatchDelta,  DocumentMatchAction,  DocumentMatchTotals,  DocumentMatchTotal,  DocumentMatchTotalLabel,} from "@/components/ui/document-match"

Two-way match#

The grid takes any number of documents. A service has nothing to receive, so it matches the order against the invoice. Here navigate is set to exceptions, so the lines within tolerance are skipped.

0exceptions0missing
0 to review
LineOrderPO-2026-0392InvoiceCT-2026-311Climatización Turia S.L.
Quarterly HVAC serviceFixed feeMatched€480.00€480.00
Call-out labour, 6 hPer hourAllowed ±2%, this line +1.8%Within tolerance€342.00€348.00+€6.00
G4 filter setPartsAllowed ±2%, this line +21.4%Exception€126.00€153.00+€27.00
ParkingNot on the orderMissingNot ordered€18.00

Tolerance#

The track is the allowed band around the reference, and the dot is where the line landed: grey when exact, amber inside the band, red outside it. Hover it for the numbers.

Allowed −10% to +2%, this line 0%Allowed −10% to +2%, this line +1.2%Allowed −10% to +2%, this line +5%Allowed ±5%, this line −8%

Card and density#

On its own page the grid needs no frame. Set variant="card" when it sits beside other panels, and density="compact" for a tighter workspace.

0exceptions0within tolerance0missing
0 to review
LineOrderPO-2026-0418ReceiptALB-7731InvoiceFV-26/1184Suministros Ebro S.L.Proposed action
Nitrile gloves, size M, box of 100GN-100-MMatched€272.0040 x €6.8040 unitsReceived 14 Oct€272.0040 x €6.80
Duct tape, grey, 50 mCA-50Allowed −10% to +2%, this line +1.9%Within tolerance€99.6024 x €4.1524 unitsReceived 14 Oct€101.5224 x €4.23+€1.92
Recycled euro palletPE-1200Allowed ±0%, this line +7.1%Exception€285.0030 x €9.5028 unitsReceived 14 Oct€285.0030 x €9.50+2 units
Stretch film, 23 micronFE-23Allowed −10% to +2%, this line +5%Exception€226.8012 x €18.9012 unitsReceived 14 Oct€238.2012 x €19.85+€11.40
Freight and packingNot on the orderMissingNot on this documentNot received€35.001 x €35.00
Thermal labels 100 x 150ET-100150Missing€120.0010 x €12.0010 unitsReceived 14 OctNot invoiced
Net€1,003.40Quantities only€931.72
VAT 21%€210.71€195.66
Total€1,214.11€1,127.38

Keyboard#

The grid is one tab stop. From a line:

  • ↓ ↑ move to the next or previous line.
  • N P jump to the next or previous variance.
  • Home End go to the first or last line.
  • Tab moves into the line's proposed action.

API reference#

DocumentMatch

The frame and the context every part reads. It tracks the active line and walks the variances. Every part accepts className, merged last.

PropTypeDefaultDescription
valuestring | null-The active line, when you control it.
defaultValuestring | nullnullThe line active on the first render.
onValueChange(value: string) => void-A line was focused or picked with the keyboard.
navigate"variances" | "exceptions""variances"What n and p jump between: every line that is not a match, or only exceptions and missing lines.
variant"plain" | "card""plain"No chrome for a page, or a framed surface.
density"comfortable" | "compact"-Cell padding. Reads DensityProvider when unset.

DocumentMatchCount

One figure in DocumentMatchSummary. Children replace the noun.

PropTypeDefaultDescription
status*"match" | "within" | "exception" | "missing"-Sets the dot and counts the lines in that state.
countnumber-Overrides the count, for a figure that has to be right on the server render.

DocumentMatchNavigator

Previous and next buttons for the variances, with the position between them. They move focus to the line.

PropTypeDefaultDescription
previousLabelstring"Previous variance"Accessible name of the previous button.
nextLabelstring"Next variance"Accessible name of the next button.
position(index: number, total: number) => ReactNode-Draws the position. index is -1 when the active line is not a variance.

DocumentMatchGrid

The scrolling table. On a narrow screen it scrolls sideways and the line column stays pinned. Forwards <table> props; give it an aria-label.

DocumentMatchDocument

A column head for one document. Compose DocumentMatchDocumentTitle, DocumentMatchDocumentNumber and DocumentMatchDocumentMeta inside it.

DocumentMatchLine

One line, read across every document. It is a stop for the keyboard: arrow keys walk the lines, n and p jump between variances.

PropTypeDefaultDescription
value*string-Identifies the line for value and onValueChange.
status"match" | "within" | "exception" | "missing""match"Drives the mark in the line header and whether n and p stop on it.
statusLabelstring-The status as read by a screen reader. Use it to translate.

DocumentMatchLineHeader

The first cell of a line, pinned on scroll. Draws the status mark; compose DocumentMatchLineTitle and DocumentMatchLineMeta inside it.

DocumentMatchCell

One document's value for the line. Only a variance is tinted; a missing cell is hatched.

PropTypeDefaultDescription
status"match" | "within" | "exception" | "missing"-Tints the cell and colours its delta. Leave it unset on the reference document.
missingLabelReactNode"Missing"Shown in an empty cell with status="missing".

DocumentMatchDelta

The signed difference against the reference, in tabular numerals.

PropTypeDefaultDescription
value*number-Positive for over, negative for under.
format(magnitude: number) => stringStringFormats the magnitude; the sign is added for you.

DocumentMatchTolerance

The allowed band as a small track, with the line's deviation marked on it and a tooltip that spells it out. Ratios, so 0.02 is 2%.

PropTypeDefaultDescription
over*number-How far over the reference is allowed.
undernumberoverHow far under is allowed.
valuenumber-The line's deviation. Leave it out to show only the band.
descriptionstring-Replaces the tooltip and screen reader text. Use it to translate.

DocumentMatchAction

The proposed action for the line, at the end of the row. Buttons inside take the small size. Render it empty to keep the column.

DocumentMatchTotals

The footer. Each DocumentMatchTotal row holds a DocumentMatchTotalLabel and one DocumentMatchCell per document; the last row reads as the total.

FAQ#

No. It draws the result of your matching engine, so the rules and tolerances stay yours. Pass each line its status and mark the cell that disagrees.