shadcn.io is not affiliated with official shadcn/ui
Recurring Payment Dialog Block for Solid
A rent page with its autopay dialog shown open: an amount, how often, a start day from a folded inline calendar, when it ends, and the next four payment dates worked out under the fields.
Installation
Overview
Let people set up a payment that repeats and see the schedule before they agree, with this autopay dialog for Solid with Ark UI. It opens from the Rent page of Rentbarrow, a rent-payment app: the page's header holds an outline Set up autopay Button, and the page itself, a rent row with its amount and due date and a short payments table, is drawn under a soft scrim inside a hairline frame with a plus mark on each corner. The Dialog takes an amount in a Number Input ($50 to $20,000), how often in a Segment Group (weekly, monthly or quarterly), a start day, and when it ends in a Select. The start day opens an inline Calendar that closes every day up to the one the block treats as now and folds away again when you pick. Under the fields an ordered list names the next four payments, worked out from the start and the frequency, and a line totals the year or the whole run. Set up autopay is the only Shine Button; it changes the rent row behind the dialog to Autopay on: the 1st of each month, with the next date. For preview the dialog is drawn open in place when the page loads; the header's button opens the real dialog. For rent, bills, donations and any recurring payment.
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 Set up autopay 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 ScheduleFields component and share the block's state. 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 payment dates worked out?
From the start day and the frequency, in UTC, from the first payment each time: weekly adds seven days, monthly adds a month and quarterly three. A monthly payment started on the 31st lands on the last day of a shorter month and goes back to the 31st when the month has one, because every date is counted from the start and not from the one before. The dates are written with Intl.DateTimeFormat in UTC, so they read the same on the server and in every time zone. The calendar only offers days after the date the block treats as now, AS_OF; point that at the real date in your app.
What does the amount field accept?
Whole dollars from $50 to $20,000. Anything else is refused with a sentence under the field when you press Set up autopay and focus goes back to it; once the message shows, it follows your typing and clears when the amount is right. The schedule and the total use the amount as typed, kept inside those limits, and the text is read the same in an Arabic page, where the number field prints Arabic-Indic digits. The card the payment comes from is a typed line: no card number is asked for here.
What stays local?
Everything. Nothing is scheduled and no card is charged: Set up autopay only changes the rent row and the page's button (it becomes Change autopay) and says, in the polite status, what was set up. The saved card is typed in the file, and a real app takes cards with its payment provider's hosted fields. In your app send the amount, the frequency, the start and the end to your server, which owns the schedule, and show the dates it returns.
Is it accessible from the keyboard?
Yes. The real dialog is named by its title and described by its sentence, focus starts on the amount and stays inside until it closes, and Escape, Cancel and the close button return focus to Set up autopay. The start day is a button that says whether the calendar is open (aria-expanded), the calendar is a real grid with the arrow keys, and choosing a day returns focus to the button. The list of payments is an ordered list with a name. The trigger is the Set up autopay button wrapped in DialogTrigger with asChild, and the dialog is controlled with open and onOpenChange.
How does it look on a phone?
The copy shown on load docks to the foot of the page as a bottom sheet with its buttons pinned and the fields scrolling above them; the fields stack and the calendar takes the full width when it is open. The real dialog keeps the Dialog's own phone layout, a sheet stuck to the bottom of the screen.