shadcn.io is not affiliated with official shadcn/ui
Budget Allocator Dialog Block for React Aria
A budgeting app's spending page with its allocation dialog shown open: a typed monthly income, six category sliders that can never overspend it, three presets, a rolling unassigned amount and a stacked bar with percentages that add up to 100.
Installation
Overview
Share a month of income across the things it pays for, without ever spending more than there is, with this budget allocator dialog for React with React Aria. It opens from the Spending page of Tallyjar, a budgeting app: the header holds an outline Button, and the page it opens over is drawn under a soft scrim inside a hairline frame with a plus mark on each corner, with three summary cells and a bar for each category. The Dialog takes a typed income in a Number Input (4,200 a month), three presets in a Toggle Group (50/30/20, Lean, Saver) and six Sliders, Rent, Food, Transport, Fun, Savings and Other, in steps of 10. Each slider stops where the income runs out, so the total never passes it; the unassigned amount rolls in a Sliding Number beside a stacked bar of foreground weights whose percentages are rounded by largest remainder, so they always add up to 100. Save budget, the only Shine Button, gives the drawn bars on the page behind the new widths. For preview the dialog is drawn open in place when the page loads; the header button opens the real Dialog, with the same state. For budgeting, saving, allowance and cost-split tools.
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 spending 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 Plan budget button in the header 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 BudgetFields component and share the block's state, so what you set 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 can the budget not be overspent?
Every slider shares one scale, from 0 to the income, but its move handler limits the value to the income minus what the other five categories hold. Drag a slider past that point and the thumb stops where the unassigned amount reaches zero; free some money from another category and it can go further. Amounts snap to steps of 10. A preset assigns every unit of the income, using the same largest-remainder rule as the percentages, so a preset leaves nothing unassigned. Lowering the income below what is already assigned is refused with a message that says how much is assigned.
Why do the percentages always add up to 100?
Rounding each share on its own can add up to 99 or 101. apportion gives every category the whole part of its share, then hands the leftover points to the largest remainders, first in the list winning a tie. The unassigned amount takes part as a seventh share, so the bar, the list and the polite sentence agree. On a phone the list of percentages is read by screen readers and a one-line summary replaces it on screen, to keep the sliders in view.
What stays local, and how do I wire it for real?
Everything. Save budget keeps the plan in local state, the drawn bars on the page behind take its widths, and a polite status message says nothing was stored. Escape and Cancel put every control back to the saved plan. In your app post the income and the six amounts from save, as whole numbers of your smallest currency unit, and load the saved plan into START. Change CATEGORIES, PRESETS and STEP to match your categories.
Is it accessible from the keyboard?
Yes. The real dialog is named by its title and described by its sentence, focus is trapped while it is open, and Escape and the close button return focus to the header button. The button and the dialog sit inside DialogTrigger, controlled with isOpen and onOpenChange, and the title is a level 2 heading. The copy shown on load moves no focus; Escape inside it and its close button close it and put focus back on the header button. Each slider is named for its category and moves with the arrow keys, Home and End, and says its amount and its share in words. The presets are a toggle group: pressing the lit one again clears its name but keeps the amounts. React Aria's number field applies a typed income when you leave it or press Enter.
How does it look on a phone?
The copy shown on load docks to the foot of the frame as a bottom sheet with Save budget pinned below a scrolling body. The unassigned amount and the stacked bar come first, then the income and presets, then the six sliders, so the figure that matters is on the first screen. The real dialog keeps the Dialog's own phone layout: a sheet stuck to the bottom of the screen.