Skip to content
shadcn.io

shadcn.io is not affiliated with official shadcn/ui

Split Payment Dialog Block for React

A point-of-sale ticket with its split-payment dialog shown open: methods added from a select, an amount for each, a running total with a bar by method, and Pay held back until the remainder is exactly zero.

Framework
UI library

Preview

Preview width

Installation

Part of Pro
Pro unlocks the code and one-line install of every Pro button and text effect, plus the MCP for your AI editor.
Have Pro already? Sign in to open it.Opens here as soon as you're Pro.

Overview

Let a customer pay one total with several methods, with this split-payment dialog for React with Base UI. It opens from Ticket 1048 of Counterfold, a point-of-sale app: the page's header holds an outline Charge Button, and the page itself, a grid of product tiles and the open ticket, is drawn under a soft scrim inside a hairline frame with a plus mark on each corner. The Dialog is a split. At the start a row for each method, added with a Native Select: a Visa ending 4242, a gift card that holds $25.00 and store credit that holds $10.00, each with an amount Input and a message of its own when it is wrong ("Gift card holds $25.00"). At the end a running panel keeps the total, what is allocated and what remains, with a segmented bar by method; on a phone it becomes a strip above the rows. The last row's Cover the rest fills what remains, up to what that method holds. Pay, the only Shine Button, stays quiet until the remainder is exactly $0.00 and says why in the line above it. Paying changes the ticket behind the dialog to Paid in 3 parts. For preview the dialog is drawn open in place when the page loads; the header's button opens the real dialog. For checkouts, restaurants and any app with split tender.

FAQ

Why is the dialog already open, and what do I keep?

So you can see it without a click. On load the dialog is a copy drawn in place over the page: a native dialog element that isn't modal, so nothing is trapped, hidden or focused, and the page around the preview keeps working. The Charge button opens the real Dialog, modal, with its focus trap, Escape and focus return, and the copy stays closed until a reload. Both show the same SplitFields component and share the block's state, so an amount typed in the copy is there in the real one. In your app keep the Dialog and drop the copy: the "in-place" state, the scrim and the dialog element inside the frame.

How are the sums kept exact?

Every amount is a whole number of cents. What you type ("49.50") is read into cents once, and the total, the allocation and the remainder are integer sums, so the remainder is exactly zero or it isn't: there is no floating-point dust to round. Text that isn't an amount, an amount of zero, and an amount above what a gift card or credit holds are each refused with a sentence on that row. The figures are formatted with Intl.NumberFormat in one left-to-right run, so a price never reorders in a right-to-left page.

What does Cover the rest do?

It belongs to the last row. It sets that row to its current amount plus what remains, but never past what the method holds: on a gift card or store credit it stops there and says how much is still to cover ("Store credit holds $10.00, so $5.00 is still to cover"). With nothing left, or with too much allocated, it says so and changes nothing. Adding a method fills its row with whatever fits, and removing one puts its share back in the remainder.

Why is Pay quiet instead of disabled?

A disabled button can't be focused or explain itself. Pay stays a real button marked aria-disabled, described by the line above it ("Allocated $74.50 of $84.50: $10.00 still to cover"). Pressing it early, or pressing Enter in an amount, speaks that line and moves focus to the first row with a problem, or to the last row when the sum is simply short. It pays only when every row is sound and the remainder is exactly $0.00.

What stays local?

Everything. Paying only changes the ticket behind the dialog and says, in the polite status, that nothing was charged. The methods and their balances are typed in the file, and no card number is typed here: a real point of sale takes cards on its terminal or with its payment provider's hosted fields. In your app load the customer's methods and balances from your API, authorize each part, and only mark the ticket paid when every part succeeded.

Is it accessible from the keyboard?

Yes. The real dialog is named by its title and described by its sentence, focus starts on the first amount and stays inside until it closes, and Escape, Cancel and the close button return focus to Charge. Each amount is named for its method ("Amount for Gift card"), each Remove button says which method it removes, and adding, removing and covering are said in the polite status inside the dialog. The trigger is passed to DialogTrigger through render, and the dialog is controlled with open and onOpenChange.

  • Pro

    Payment Method Dialog

    Payments and plans

    A ticket shop's order summary with its payment-method dialog shown open: a saved card, a bank transfer and an invoice as rows, each asking only for the one detail it needs, and a Pay with row behind the dialog that changes when you choose.

  • Pro

    Split Bill Dialog

    Payments and plans

    A group-dining app's open check with a dialog that splits it by item: each item has a row of initials that toggle who shared it, and beside them what each person pays, reconciled to the cent, with the tax and tip spread fairly.

  • Pro

    Gift Card Dialog

    Payments and plans

    A café's gift cards page with its builder dialog shown open: a card drawn in greys that reads as you build it, preset or custom amounts, three printed patterns, a recipient and a message, and delivery now or on a day picked in an inline calendar.

  • Pro

    Apply Coupon Dialog

    Payments and plans

    A cookware shop's basket with its promo-code dialog shown open: one field that judges five typed codes with five different answers, applied codes kept as removable chips, and a summary that is worked out again after every change.

  • Pro

    Quick Transfer Dialog

    Payments and plans

    A payments app's account home with its send-money dialog shown open: five recent people in a scrolling row, an amount with quick chips, a note, the fee and balance after, and a review step before Send.

  • Pro

    Add Card Dialog

    Payments and plans

    A transit wallet's payment methods page with its add-card dialog shown open: a card drawn in greys that fills in as you type, a number that groups itself and names its network, checks on the number, expiry and code, and an Add card button that keeps only the last four digits.