shadcn.io is not affiliated with official shadcn/ui
Day Calendar Skeleton Block for Vue
A clinic's day from 08:00 to 19:00 that loads in place: real hour lines under five appointment boxes that become names, kinds and times, with arrows to the next typed day.
Installation
Overview
Show a day's schedule while its appointments load, then swap them in without moving anything, with this day calendar skeleton for Vue with Ark UI. A hairline frame with a plus mark on each corner holds a band with the day's name and a previous and a next button, over a timetable from 08:00 to 19:00 for Isogon, an appointment scheduler for clinics. The day, the hour labels and the hour lines are real from the first paint; each of the five appointments is a Skeleton box spanning the same hours that becomes the person, the kind and the time range in the same place, in hour order, when you press Load. The arrows reload the neighbouring typed day. The control is an outline Button, the bands are ruled by Guides, and a polite line says what loaded. It's built from Skeleton, Button and Guides, with a CSS entrance only. For appointment books, rotas and any single-day view that waits for a query.
FAQ
What stays local, and how do I wire it to a real query?
Nothing is fetched. The three days of appointments are typed in DAYS, Load starts a 700 millisecond timer, and when it ends the phase becomes ready and the data swaps in; Reload goes back to the bars and in again. To wire it for real, delete the timer, the Load button and the polite line, and set data-state (loading or ready) and aria-busy on the region from your query's pending state, or use the bars as a Suspense fallback and render the real appointments when they arrive. The bars are derived from the same array as the data, so keep one component for both.
Why doesn't the timetable move when the appointments arrive?
Each bar and its data sit in one grid cell, so the larger of the two sets the cell's box and the swap changes nothing around it. The timetable is a fixed grid of 22 half-hour rows, an appointment sits in the rows it spans by two CSS variables, and its box and its text share one cell, so a day with other appointments is exactly as tall. The heads, labels and frame are real in both phases, and the polite line reserves two lines, so the block is exactly as tall before, during and after loading at every width.
How are the appointments placed?
Each one carries a first row and a span in half hours (row: 5, span: 3 is 10:00 to 11:30). A module helper turns them into the CSS variables --r and --n, and the classes row-start-(--r) and [grid-row-end:span_var(--n)] put the box in the grid. Change the hours in the data and the box and its skeleton move together.
What do the previous and next buttons do?
They reload the neighbouring day: the block holds 3 typed days (Wed 14, Thu 15 and Fri 16 October 2026), and pressing an arrow goes back to the bars and then to that day's data. At the first or last day the press does nothing but say so in the polite line. Nothing else is interactive: you can't create or move anything here. In your app, set the day in your router or state and let your query fall back to the bars while it loads.
Is it accessible?
The region is marked busy while it loads and carries a screen-reader-only "Loading appointments…", and the polite line announces what loaded once. The bars are hidden from assistive technology and the layer that isn't showing is invisible, so it is neither read nor focusable. The pulse stops when the data is in, and under reduced motion there is no pulse, no spinner and no stagger: the swap is instant. The hour labels are decoration for sighted readers: each appointment names its own time, and the arrows are named "Previous day" and "Next day".