Components
Controls
format
align
order
theme
One name, handed to applyTree. The sheet holds all
three palettes — @vars marine { --page: … }
beats the unscoped @vars — and every rule in the
file names a role rather than a colour. So nothing in the tree gains
or loses a class and nothing moves: a theme is forty-one colours,
not a second set of rules. ?theme=sunrise lands on one
directly.
effects
The background, from
lib/evg/gl/effect-presets.css — the blocks
that are already a look. Picking one types it into
the stylesheet below and the cascade reads it from there; a
bubbles preset lands on the pane rather than on the sky, because
it draws what is behind an element and the sky would paint
over it.
Each one is an element that declared an effect. Turning one off sets a flag the painter reads: the pass is skipped, the shader never runs, and a press on a sleeping card goes nowhere. Nothing is rebuilt to stop drawing one shader.
effects.css — live
evg-fx-density, -hue, -speed on .fx-sky, or -sweep-speed on .fx-glass. Change evg-surface-effect to plasma-wave, bubbles or ambient-light and paste a block from lib/evg/gl/effect-presets.css to see another one.
what to try
Click a header three times: ascending, descending, then back to the order the data arrived in. A two-way toggle can never reach that third state.
Spend is numeric, so its first click sorts descending — biggest first is the useful first answer for a quantity, A-to-Z for a name.
The header checkbox is page-scoped: select page one, go to page two, and the first page stays selected.
what to try
Click a row, then use the arrows. → on a closed folder opens it and stays put; on an open one it steps to the first child. Two meanings, one key, decided by the row.
→ on a leaf is not a no-op — it steps down, the same as on an open folder. That is what the library does, and the first reading here said otherwise.
End goes to the last visible row, which is the deepest open descendant and not the last top-level item. Enter and Space toggle a folder and select nothing.
what to try
Open the menu, then rest the pointer on Available. The submenu waits 100 ms before opening — measured off Radix, and the reason dragging down a menu does not flash every submenu on the way past. Moving away closes it at once.
From the keyboard: ↓ to step, → or Enter to go in, ← to come back out, Esc to close everything. Arrowing in puts you on the submenu’s first item; hovering in leaves you on the row you are pointing at. The asymmetry is Radix’s, not ours.
→ on an ordinary row does nothing, and neither does ← at the top level. A vertical menu’s horizontal arrows are for nesting and nothing else.
what to try
Tab visits every trigger — an accordion does not rove. ↓ / ↑ step between the triggers of one list and wrap at the ends, Home / End jump to the first and last, Enter or Space toggles.
In the two single lists opening an item closes the other. The Multiple list keeps them all.
what to try
Press a range button, or focus one and use the
arrows. The chart is not a picture: it is a Vega specification run
by Vela, turned into draw commands, and appended
to this page’s display list by VlEvgList. Ninety
points go in as data and the outline that comes out is the one
Vela computed.
There were two routes. An element can carry the SVG in
svgSource and EVGDisplayList imports it
— and that drops every label, because the importer
walks vector items and a <text> is not one:
forty-seven commands of geometry and not one axis tick. A picture
with no numbers on its axes still looks like a chart, which is why
the gate counts words as well as outlines.
The chart is therefore not in the element tree. It is painted at the box the layout reserved, which is why the placeholder has an id and no children: the tree owns where it goes and Vela owns what is drawn there.
The range is a radio group and not a toggle group, and the difference is measured: clicking the chosen item of a toggle group deselects it, and a range picker with no range picked is a chart with nothing to draw.
what to try
The other form shape: label to the left, every control’s left edge on one column. The rows are deliberately three different heights — a text box is 38, the photo row 40, the switch row 22 — so a layout that only worked when they matched would have nowhere to hide.
Tab and type. The clock in the time field is a leading icon and the calendar a trailing one, and the caret has to know: a leading icon moves where the run starts.
Pick a date. The calendar at the end of Birth Date and Availability Date is a button: click it, or press Enter or Alt+↓ on it (or Alt+↓ in the field), and the calendar demo’s own calendar opens under the field on the month of its value. Choosing a day writes it in the field’s format (the time after the date is kept); Escape or a click outside closes it and puts you back in the field.
The text itself is still free text. Segmented editing — where ArrowUp on the month changes the month and not the caret — is its own contract, and it is not built here rather than half-built.
what to try
Three effects, each one line of CSS on the element that has it.
Press either dark card and the water is that
card’s alone — the third declares nothing and stays
still. The sky drifts on its own; the pane over it
refracts what is behind it rather than blurring it, and
the two lines inside the pane are drawn after it, so they stay
sharp. Nothing in the demo’s code holds a shader, a clock or
a coordinate: evg-surface-effect says what runs,
evg-effect-on what starts it, and the box comes from
the layout.
A surface effect is a GPU pass, so the PNG snapshots of this page and the software rasteriser draw the boxes and the text and none of the effects.
what to look at
Nothing here moves or takes a press: a separator is a picture of a
boundary, not a control, so Tab passes over this demo.
What differs between the six cards is what the rule means.
The rules under “Design System”, between the three menu
columns and through the order summary are
role="separator" with an aria-orientation;
the bars between Blog, Docs and Source, the hairlines between list
rows and the line under “or continue with” are
decorative and leave the accessible tree, as Radix’s
decorative prop does.
The vertical rules are not given a height: they stretch to the row they are in, so the menu’s rules are as tall as its two-line columns. On a phone the columns wrap their second line and the rules grow with them.
what to try
Press a card and drag it: up and down within its column reorders it, into another column moves it. A copy follows the pointer, a dashed placeholder shows where it will land, and the column counts change as it crosses. Let go to drop it there.
Or Tab to a card and press Space (or Enter) to pick it up. Arrow Up / Down move it within the column, Arrow Left / Right to the neighbouring column at the same position; Space drops it and Escape puts it back where it was. Each step is announced in the status line under the board, e.g. “Picked up Design landing page. Column To Do, position 1 of 3.”
what to try
Right Drawer slides ReUI’s panel in from the right over a dimmed, blurred page. Its ten paragraphs scroll inside it — wheel, arrow keys, Page Up/Down, Home, End — while the title and Submit / Cancel stay put.
Drag a panel towards its edge (the header, the text, the handle): it follows the pointer. Let go past a quarter of its size, or flick it, and it is dismissed; let go early and it snaps back. The buttons are buttons and do not start a drag.
Tab stays inside an open drawer. Escape, a click on the dimmed page, Cancel or × close it and hand the focus back to the trigger. Open Drawer steps the goal with − / +, Menu picks a section (arrow keys walk it), Notifications drops from the top, and Edit preferences is a dialog on a wide demo and a bottom drawer on a phone.
what to try
Drag a window by its title bar; it keeps enough of itself on the desktop to be grabbed again, however fast the pointer goes. A click anywhere in a window brings it to the front. Resize from any edge or corner — the cursor changes over them — down to a minimum size and no further out than the desktop. Drop a window on the left or right edge to fill that half.
The title bar buttons minimize (to the dock), maximize / restore and close; a double-click on the title bar maximizes too. The dock brings a window back, reopens a closed one, and minimizes the one on top. Settings opens a modal dialog: Tab stays inside it, Escape closes it and the focus goes back to the button that opened it; it moves by its title bar as well.
Keys: Tab walks each window (title bar, its buttons, its content) and then the dock, whose arrows move along it. Alt+Space (or Tab to a title bar) then the arrows move the window 10px, Shift+arrows 1px, and Ctrl+Shift+arrows resize it; Enter or Escape finishes. Ctrl+F6 (or F6) goes to the next window, Shift the other way. Each move and size is announced in the status line in the dock. On a phone the windows open as full sheets; drag a title bar to bring one back to its own size.
what to try
Move the pointer over the stars in Interactive: they preview the rating under the pointer and go back when it leaves. Click to set it; the 4.0 label follows. In Half-star precision each star is two targets, so the left half of the fourth star is 3.5.
Tab to an editable rating: it is one slider stop.
Arrow Right / Up add a step, Left /
Down take one away, Home is 0 and
End the maximum; a reader hears
aria-valuetext “4 out of 5 stars”. The
read-only ratings (the sizes, the summary, Read-only)
are images named “Rated 3.5 out of 5 stars” and take
no input.
what to try
Every card works. Click a page, Previous /
Next or the first / last arrows: the active page
moves and the ellipsis follows it (card 4 keeps three pages at each
end, the others a window of three or four). At either end the
arrows fade and become aria-disabled.
Type a page into Go to page and press
Enter or leave the box: a number past the end is
clamped to the last page, an empty box or 2.5 turns it
red with aria-invalid and a message, and
Escape puts the current page back. The
Rows per page select changes the range text
(“1-25 of 100”) and the page count; the
Page select jumps.
Each card is a navigation landmark named
“pagination, …” (shadcn’s name plus which
card, so ten landmarks are not ten of the same) holding a list;
the ellipsis is read as “More pages”; the current page is
aria-current="page". Tab walks the links (Enter or
Space follows one); a phone drops the Previous / Next words, as
shadcn’s hidden sm:block does.
what to try
Click anywhere on a row or card: the whole of it is the radio, and the checked one turns grey with a darker edge. Each card is its own group, so checking Phone leaves English alone.
Tab onto a group: it is one stop, and it lands on the checked radio. ↑ / ↓ (or ← / →) move and check, wrapping at the ends, as the WAI-ARIA radio group asks; Space checks the focused one. The next Tab goes on to the next group.
Each group is a radiogroup — “Battery
Level” is named by its legend (aria-labelledby),
the others by an aria-label — and every row a
radio with aria-checked. On a phone the
cards stack in one column; the 2×2 grid keeps its two.
what to try
Click Password, or Tab onto the strip and use ← / →, Home and End: the panel follows the focus (automatic activation, as Radix does). The strip is one Tab stop; the next Tab goes into the card’s fields.
Edit a field and press Save (or Enter in the
field): “Saved” appears in a role=status
region. The vertical card below moves with
↑ / ↓ instead.
what to try
Press Next without choosing: the question stays and
says “Choose an option to continue.” under the options
(the group is aria-invalid and described by it). Pick an
option — click it, or press its letter — and Next moves on;
“Question 2 of 3” follows and a status region announces
the new question. The third question of each card is an optional
1–5 scale; Finish shows the summary with
Back and Start over.
From the keyboard: Tab reaches each card’s options as one stop, then Back and Next. A–F pick (single choice) or toggle (multiple choice) the option with that badge, 1–5 on a scale. In a radio group the arrows move and select (WAI-ARIA); in the checkbox group they only move the focus and Space toggles, and the “0 of 6 selected” badge counts and is announced. Enter is Next, and after it the focus is on the new question. With reduced motion on, the steps change without the slide.
The third card is drawn from the file at its left. Answer it and press Finish: the answer is checked and saved (“Saved to the deck”), and the bars at the right count it. Next respondent starts a new answer; Add 20 answers fills the bars, Clear empties them.
what to try
Click the swatch in Native-style popover: Chrome’s colour dialog opens under it. Drag in the square (saturation across, brightness down), drag the rainbow for the hue and, in the other two, the checkered bar for alpha. The small ⇅ button beside the fields switches HEX → RGB → HSL; type into a field and the colour follows, a value that does not read goes back when you leave the field. Enter keeps the colour and closes, Escape closes and puts back the colour it had when it opened.
Press the pipette, then move over Something to sample:
a loupe shows the pixels under the pointer ten times larger, with the
centre pixel outlined and its hex under it. Click (or
Enter) to pick it; the arrow keys move the point one
device pixel; Escape cancels. The loupe reads the
WebGL canvas the demo is drawn in, so it picks anything in the demo
and nothing outside it: a web page cannot read the rest of the
screen. Where the browser has the EyeDropper API
(Chromium), a second button, Pick from screen, opens
the browser’s own picker, which can.
From the keyboard: Tab walks the square, the pipette, the hue and alpha sliders, the fields, the format button and the swatches (one radio group). In the square the arrows step saturation and brightness by 1% (Shift: 10%); the sliders take the slider keys; ↑ / ↓ step a number field. A reader hears the square as a 2D slider, “Saturation 76%, Brightness 96%”.
what to try
Click Favorite Fruit, or Tab to it and press Enter, Space or ↓: the list opens under the trigger with the highlight on the chosen row. ↑ / ↓, Home / End and the pointer move it; type b, then b again, to jump between Banana and Blueberry. Enter or a click chooses and closes; Escape, Tab or a click outside closes and gives the focus back to the trigger.
In Invalid state press Submit (or leave the select) with no fruit: the label, the border and the ring go red and the error takes the description’s line; pick a fruit and it clears. Status toggles a row per click or Enter and stays open, the dots and the count following. The avatar list scrolls when there is not room for all ten, and a list near the bottom of the page opens above its trigger.
what to try
Click Framework: the list opens under the box. ↓ / ↑ or the pointer highlight a row (past the last one the arrows go back to the box, then wrap), Enter or a click chooses it and a ✓ marks it. Type zz: “No framework found.” — then Escape or Tab: the box goes back to the chosen label, because the value is the item, not the text.
In Frameworks a pick adds a chip and the list stays open; Backspace in the empty box takes the last chip, ← steps onto the chips and Delete removes one. Status has a × that clears it and a Canceled row you cannot choose. Type a new word into Label and pick Create “…”.
what to try
Type an into Fruit: the list opens under the box with the matching letters in bold. ↓ / ↑ or the pointer highlight a suggestion, Enter or a click completes the text with it. Enter with nothing highlighted keeps what you typed — the text is the value.
Escape closes the list; a second Escape empties the box. Tab leaves without taking anything. Type ty into Language: the rest of “TypeScript” is written in, selected — keep typing over it, or Backspace it away. Movie answers after 450 ms (“Searching…”), and zz anywhere shows “No results found.”
what to try
The M-Files metadata card, on the kit. Customer is a combobox with chips: click it and type, pick with the arrows and Enter (the list stays open), press Backspace in the empty box to take the last chip, or ArrowLeft onto the chips and Delete one. Class is the same control with one value: type “zz” and Tab away and it reverts to the chosen label. Every one of those rules was measured on Base UI’s Combobox.
Save with an emptied required field: the error takes the hint’s line, and the control announces it instead of the hint. Discard puts everything back.
what to try
Tab between the fields and type. The caret and the
blue selection band are positioned with
measureRun(value[0..i]) — the same measurement
the painter makes of the same string, so the bar is between the two
characters it claims to be between rather than near them.
The Amount field opens focused with its contents selected, which is the one state that shows a caret and a band at once. The controller was measuring at 14px against text drawn at 13, and the band ran four pixels past the number it was selecting — the breadcrumb’s font-family bug, repeated with the size.
The password’s value is never its glyphs:
value is what a reader and the conformance trace get,
glyphText() is what is drawn, and nothing joins them.
The eye flips the second and leaves the first alone.
A field in error is described by its error and not also by its hint. Reading both leaves a reader to work out which one still applies — and the hint’s line is where the error goes, so the picture says the same thing.
Fix the Email field. Its error is decided
from the value on every rebuild, not written once: type an @ and a
dot and the ring, the sentence and aria-invalid all
stop claiming it is wrong together. Each message says what is
wrong rather than that something is.
Find a customer is a ComboboxCtl.
Type a few letters to filter the list, ↑ ↓ to walk it,
Enter to take a row into the box; leaving it with a half-typed
query puts the chosen label back. The arrows reach the list rather
than the caret because the demo asks for them — the
text bridge leaves them with the platform everywhere else.
what to try
Click a day, then click it again. A single-select calendar toggles — choosing the chosen day clears it — and the field above empties to show that it did. Measured against react-day-picker, which is what shadcn’s Calendar wraps; guessing here gives you a date you cannot un-set.
Home and End move within the week, not the month. From Friday the 1st, Home is April’s 26th — an outside day — and the caption follows it. That one row of the capture is the reason this component has an oracle at all: “Home goes to the 1st” is the obvious implementation and it is wrong.
PageUp from the 31st lands on the 30th of April, because April has no 31st. The page keys carry the day number and clamp it; subtracting thirty-one days agrees here by luck and disagrees in most other months.
Type into the date field. It is a
mm/dd/yyyy editor now, not a label: “1” then
“3” in the month gives 12 and moves to the day, ArrowUp
on 12 wraps to 01, Backspace empties a segment without moving, and a
complete date turns the grid to its month with the day chosen.
Measured against Chromium’s own <input
type="date">, segment by segment through the
accessibility tree, in ui:datefield:check.
The tab stop at rest is today, not the first of the month
— also measured, and also not what this was first written as.
Two behaviours are deliberately not copied: the
reference’s roving tabindex loses track of focus, and it emits
no aria-selected. Both are recorded as divergences and
counted as such rather than quietly scored as parity.
what to try
Click a slot and type six digits. The active slot
is ringed, the first empty one carries the caret, and Verify lights
when the sixth digit lands. A seventh digit replaces the
sixth rather than being refused — measured against
input-otp, the library behind shadcn’s Input OTP.
Home, then a digit. The selection in this component is never a bare caret inside the value: a caret on a character becomes a range over it, so typing there replaces and moves on. Backspace on a middle slot leaves the slot before it active, because a caret that moved left settles on the character to its left. Both are the reference’s rules, reproduced.
A letter into the code does nothing; the invite field below takes any character. Tab leaves the code and Shift+Tab comes back at the end, wherever you left from — the reference’s focus handler puts it there. One thing is deliberately not copied: a click on a slot while the field already has focus selects that slot here; the reference lets the browser place a caret in its invisible squeezed text and the capture shows slot 0 clicked selecting slot 3.
Not wired on this page: the clipboard. The controller’s paste is measured (a code over a half-filled field, a code with punctuation refused whole, a long one cut to fit), but this field has no text-input session yet, so Ctrl+V on the canvas does not reach it.
what to try
Click a value, pick something else, and watch the list
under it. The count and the rows are
FilterCtl’s answer, not a second one drawn to
look like one — which is the only way to tell a filter bar
from a picture of a filter bar.
Deselect everything in a chip. It does not
empty the list. TanStack drops a filter whose value is empty rather
than matching nothing, so a half-built chip shows every row —
and the obvious code goes the other way, because
[].includes(x) is false. Measured down both of
TanStack’s routes, which disagree: that cleanup lives in
setColumnFilters and nowhere else, so a bar holding its
own rule tree never gets it for free.
The assignee chip holds four people and draws three. “+1” is the overflow rule from the reference screenshot, on the page rather than only in a gate.
Arrow keys move between chips; Backspace removes the focused one. The first ArrowRight moves — the tab stop is already on the first chip, and a version that spent it adopting focus is the defect the controller’s gate caught.
⋮ opens the chip’s menu. Change the
operator, edit or clear the value, or remove the filter —
the same MenuCtl the dropdown demo runs, so Enter or
Space opens it (on the chip or on ⋮), the arrows walk it and
Escape hands focus back to the button.
what to try
Open Settings. Every switch and select changes the calendar: hide the weekends, number the weeks, shade the off days, put an add button on every day, start the week on Monday. In a week, day or resource view the Time grid tab sets the day's hours, the grid interval and the drag snap.
Drag an event in a week to move it (across days too), drag its bottom edge to resize it, and drag on an empty slot to create one — all on the snap you chose, and each switched off by its Behavior toggle. From the keyboard: focus an event, then ↑/↓ move it, Shift+↑/↓ resize it and ←/→ move it a day (or a person). Escape cancels a drag.
Region: six languages — Arabic turns the whole calendar right to left — and a time zone that moves every clock time, across midnight where it has to.
The booking board scrolls inside its card; drag a booking to another room or stretch it. New booking adds a check-in at noon in 101.
The measured week, at the bottom:
look at the three boxes stacked at 16:00. They all
end at the same right edge, and each one is narrower than
the last — 100%, 66.67%, 33.33%. Almost every hand-written
calendar gives three overlapping events a third of the width each,
and that is a visibly different calendar. Measured from a rendered
@schedule-x/calendar, with three events rather than
two, because two are 50/50 under either rule.
The 10:00 event is full width. It starts exactly
when the 09:00 one ends, so it takes that column back rather than
opening a fourth. Touching is not overlapping — a calendar
testing with >= squeezes the whole morning to a
quarter for no reason a person can see.
“On call” runs 23:00 to 01:00 and is in the all-day band, not the grid. A timed event crossing midnight is promoted to a multi-day span. The plausible guess is two boxes, and the plausible guess is wrong.
The bands use the other overlap rule. They stack by first fit over inclusive dates, so two that share a single day cannot share a lane. Two overlap rules in one component, and using either one twice is wrong in one place.
Switch views freely. Day, Week and Month are always in the header, and ‹ › step by the view’s own unit — a day, a week, a month. Today comes back to the demo’s week. A crowded month day shows two chips and “+N more”, which opens that day.
Keyboard: Tab walks the arrows, Today, the views and then every event; Enter or Space presses what has the focus; ← and → step the range. A title too long for what shows of its box ends in an ellipsis.
The two patterns follow the ReUI code and screenshots the user supplied (reui.io itself is refused by the egress proxy); the measured week at the bottom is where the overlap placement they all use was taken from.
what to try
Click anywhere to step the timeline on, and watch
the rail. The dot and the line do not flip together: at
step 3 the third dot is filled and the line under it is still pale,
because the two are different predicates —
step <= value for the dot,
step < value for the line.
That is the whole finding, and it came off a picture of the reference rather than its source, which could not be reached. The obvious implementation gives both the same flag and draws a dark line under the current step.
The icons are lucide's own files, imported whole.
stroke="currentColor" resolves to the hosting
element's fill, so one copy of the artwork is dark on a pale dot and
white on a filled one.
what to try
Edit profile, then type: Name and Username are real fields (the browser does the editing, as on the form demos). Tab stays inside the dialog — the fields, Cancel, Save changes, × — and Escape, ×, Cancel or a click on the dimmed page closes it and hands the focus back to the trigger. Save closes it and says what was saved; Cancel throws the edit away.
Share → Copy puts the link on your clipboard and says Copied. The link field is read-only: you can select it, not change it.
Terms of service scrolls inside the dialog — wheel, arrow keys, Page Up/Down, Home, End — while its title and buttons stay put.
Delete account opens an alert dialog: the focus starts on Cancel, not on the destructive button, and a click on the backdrop does not dismiss it (Escape does), as in Radix’s AlertDialog. Continue deletes nothing.
what to try
Click a menu, then move the pointer along the bar: with one menu open, hovering another trigger switches to it. A click on the open trigger, anywhere outside, or Escape closes it (Escape puts the focus back on its trigger).
Tab onto a bar, then ← / → walk its triggers and Enter, Space or ↓ opens a menu on its first item. Inside, ↑ / ↓ walk the rows (skipping separators and disabled rows), Home / End jump, a letter jumps to the next row starting with it, and ← / → move to the neighbouring menu.
In View the rows are checkboxes and in Theme a radio group: choosing one toggles it (or checks it) and closes the menu, as Radix does. Sign out is the destructive variant. In the third bar, rest on Share or Find (or press →) for the submenu.
what to try
Open popover opens shadcn’s Dimensions panel under its button with the focus in Width. The four fields are real (the browser does the editing), and the line under the button follows what you type.
A popover is not modal: nothing behind it is dimmed or locked. Tab goes from the trigger through the popover’s fields and on out of it, and focus leaving it closes it. Escape, the trigger again, or a click anywhere outside closes it and gives the focus back to the trigger.
The bell opens a notifications list: press one to mark it read, or Mark all as read; the badge counts down. Under Placement, the ⓘ button opens a short text popover and Top / Right / Bottom / Left open on that side. Narrow the window: a popover that would leave the demo flips to the other side or slides back in.
what to try
Click the field at the bottom, type, and press Enter (or Send). Your message lands on the right, the transcript follows it to the bottom, and a moment later Oliver starts typing — three pulsing dots — and answers.
Scroll up with the wheel before a reply arrives: the transcript
stays where you left it and a Jump to newest
button appears instead. That policy is ScrollerCtl’s.
what to try
Puts every dot, bar and tile back at its start and sends it off again — there, back, and there again. The Replay button above the panels does the same.
what to look at
The seven dots leave together and arrive together. Everything between is the timing function, and the curve beside each one is sampled from the very function that moves it.
The cubic-bezier row goes past its target and comes back — its control points leave 0..1, and a solver that clamps would show a straight arrival instead.
Drag a row onto another and the list reorders — and it is
rebuilt, not moved: the page holds an order and hands it over, and the
tree literal builds whatever it is given. Click a row and use Space, ↑
↓ and Escape for the same thing from the keyboard. The behaviour is the
one gallery/evgui/src/SortableCtl.rgr is measured against, item for
item, with @dnd-kit/sortable as the reference.
The state is not this demo's. Sorting, page-scoped
selection and the paging arithmetic all live in
gallery/evgui/src/TableCtl.rgr, which the conformance
harness measures against @tanstack/react-table — the
library ReUI's table is built on. This file is the other half: a
treefactory and a stylesheet, and nothing else. The
controller decides what is true; the tree literal decides what it
looks like.
The rows are siblings. There is no group per folder:
the visible rows are one flat list and the nesting is carried by
aria-level alone, because that is what
@headless-tree/core renders and therefore what ReUI's
tree renders. The indent is a spacer this demo adds; a reader never
sees it and does not need to. Open/closed, focus and every key are
gallery/evgui/src/TreeCtl.rgr’s.
This demo owns no state at all. Open/closed, the
roving focus, the submenu stack, the 100 ms hover delay and
every key are gallery/evgui/src/MenuCtl.rgr’s, which
five conformance specs measure against
@radix-ui/react-dropdown-menu. What is here is a
treefactory, a stylesheet, and which icon goes on which
row. Compare it with the Menubar demo, whose keyboard is
hand-written and matched against nothing.
Each list is an AccordionCtl. Which
items are open, type="single" versus
"multiple", collapsible and every key are
gallery/evgui/src/AccordionCtl.rgr’s, written
against @radix-ui/react-accordion: triggers are buttons
with aria-expanded and aria-controls, the
open panel is a region labelled by its trigger. The page grows and
shrinks with the open items. Panels open without a height
animation, which EVG’s transitions do not cover.
The behaviour is the controllers’. Open and
closed, where the focus goes on open, Escape, the overlay press and the
focus back to the trigger are DialogCtl’s and
AlertDialogCtl’s (src/), which the
conformance specs measure against @radix-ui/react-dialog
and @radix-ui/react-alert-dialog. The page adds the trapped
Tab ring and the clipboard write; the look is shadcn’s, read from
its source.
The behaviour is the controller’s. Open and
closed, the focus into the content on open, Escape and the focus back
to the trigger are PopoverCtl’s (src/),
which the conformance specs measure against
@radix-ui/react-popover. The page adds the non-modal Tab
walk and the outside press; where each popover sits (side, align, the
4px offset, the flip and the shift) is EVG’s overlay layout. The
look is shadcn’s, read from its source.
Hover the cards at the bottom. Nothing in
MotionDemo.rgr knows what a pointer is: the tree is boxes
with class names, and every hover, press and journey on this page is a
rule in motion.css. The page’s only job is to turn a
theme over — three times, 1.7 seconds apart, and again from the start
whenever Replay is pressed — and to hand the engine
the real elapsed time each frame.
Cross the slow card quickly. Its transition is 900ms,
and leaving half way through does not take another 900ms to come
back — a reversal is shortened by how far the value actually
got, measured after the easing curve rather than off the clock. On
ease-in at the midpoint those differ by nearly a factor of
two.
The I and U are plain letters. Italic and underline
are toggles like bold and carry their pressed state, but EVG has no
font-style and no text-decoration yet, so
the buttons cannot draw the letter the way the reference does. The
three alignment buttons are drawn icons, and exactly one is pressed.
The behaviour is the controllers’. Each bar is a
MenubarCtl (src/) — which menu is open,
the triggers’ roving focus, the keyboard open and the arrows
between menus — measured against @radix-ui/react-menubar;
the inside of each menu is a MenuCtl (arrows, Home/End,
typeahead, submenus, checkbox and radio items, separators), measured
against Radix’s dropdown menu. The page only passes presses,
hovers and keys through; the look is ReUI’s, from its screenshots.
How this page works
Every demo is a treefactory declaration naming its own
tags over the same EVGElement, painted by EVG’s WebGL
painter. The demos keep their trees between frames rather than
rebuilding from scratch on every change — a transition remembers
where a value was, and a freshly built element remembers nothing. On a narrow
screen a demo is laid out at the width there is, through the
@media rules in its own stylesheet, rather than drawn at
desktop width and shrunk.
And read it. A canvas hands a screen reader one empty
graphic no matter what was drawn into it, so the page also publishes what
the frame means: EVGA11yFromTree walks the same
elements and emits roles, names, states and rectangles, and
evg-a11y.js mirrors those into transparent DOM over the canvas
— which is what a reader actually walks. Nothing is described twice: the
roles are on the treefactory tags beside the classes. A reader
activating a node is answered by pressing the app at that node’s
rectangle, so it takes the same path as the mouse.
Menus, popovers and listboxes float over the page because EVG has overlay surfaces: a menu’s content is still its parent’s child in the tree, and layout takes it out of the flow, finds the anchor among its own siblings and moves it and its subtree there. A submenu is the same thing one level down. A surface near an edge flips to the other side of its anchor and slides along the edge to stay on the page.