shadcn.io is not affiliated with official shadcn/ui
Rate Limit Error Block for Radix UI
A full-screen 429 for an API key over its limit: a 10 by 10 grid of the last 60 seconds of requests, the Retry-After header worked out from the oldest, and a demo clock that empties the oldest cells so Retry works.
Installation
Overview
Show a developer why they were stopped and exactly when to come back, with this rate limit page for React with Radix UI. It's the 429 screen of Mapwren, a geocoding API: a whole screen with the page's rails and one band across it, split in two. At the start, the code as display text, the heading, the limit and what the key has used, the response header it would get (Retry-After: 38, worked out from the oldest request) and the actions: a Shine Button, Retry, and a demo control, Let 10 seconds pass. At the end, framed by Guides, the sliding window as a 10 by 10 grid: one cell for each of the last 100 requests, each with a typed age and drawn lighter as it ages. Letting time pass moves a typed clock, the oldest cells empty first, and Retry takes the first free one. It's built from Button, Shine Button and Guides, with a CSS entrance only. For any API with a per-minute limit.
FAQ
What is a sliding window, and why is it a grid?
A request counts against the limit for 60 seconds after it was sent, so slots free one at a time as the oldest requests age out, not all at once at the top of the minute. The grid shows that: 100 cells, one per request, ordered oldest first, and the cells that leave the window are the first ones. Tones show the age: solid under 20 seconds, lighter at 20 to 40 and lighter again at 40 to 60.
Why is there no timer?
Because nothing in a page like this should tick on its own. The clock is a number the visitor moves: Let 10 seconds pass adds 10 to it, and everything else (the ages, the free count, Retry-After) is derived from the request times and that number. Start over puts it back at the moment of the error. A real page would show the header your limiter sent, and could count down from it.
How is Retry-After worked out, and what does Retry do?
Retry-After is the 60 seconds minus the age of the oldest request still in the window, 38 here because the oldest was sent 22 seconds before the page loaded. While no cell is free, Retry says "Still over the limit. Retry in 38 seconds of demo time. Nothing was sent." Once one is free, it takes the first free cell as a new request and says "Accepted in this demo: 13 remain." Nothing is sent anywhere.
How do I wire it to a real limiter?
Return a 429 from your server with a Retry-After header in seconds, and give the page the request times from your limiter (a sorted set of timestamps per key works for a sliding window). Replace the typed COUNTS with them, keep LIMIT and WINDOW, and the header, counts and grid follow.
How do screen readers read the grid?
The cells are a drawing, hidden from screen readers; the same facts are in the text: the limit, "100 of 100" in the window, the header, the free count in the status line after each action and a caption for the figure. In a right-to-left layout the grid keeps its order, because it is a time order and not text.