The interface library of the REDZ products: plain CSS and small JavaScript on the browser platform - no framework, no build step.
Status: 0.18.2 (2026-11-06) - see Releases. Three demo screens in reference/demo/ -
a list, a resource page and a settings form - show it in light and dark themes, left-to-right and right-to-left.
Site: redz-tech.github.io/REDZ.UI - documentation and the demo screens of
the latest release at the root, of the main branch under next/, and of every release from 0.17.0 under its version.
Why: why our own library instead of Bootstrap 5.3 or 6 · по-русски.
- Tokens, a reset, cascade layers, components and utilities with Bootstrap 5's names, written as plain CSS files. The
utilities are generated from one vocabulary file (
tools/vocabulary.json,php tools/build.php) and committed, so nothing is built where the library is used. - Behavior of the widgets that the platform does not give by itself - menus and tabs by keyboard, notices, a confirmation, busy buttons, a dialog closed by a click outside it - as small JavaScript modules that attach by delegation (see Scripts).
- Two files a project serves, made by our own PHP tool (
tools/min.php) and committed:dist/css/redz-ui.min.css, the stylesheets in one, squeezed, anddist/js/redz-ui.min.js, the modules as one ES module with short local names; each package's files beside them,dist/css/redz-ui-charts.min.cssand the rest.dist/is the folder a project receives, as it is. Each pass proves its result, the probes run whatdist/holds in every engine, andcss/,js/and the packages' folders stay in the repository to read. - Light, dark and system themes; left-to-right and right-to-left from one stylesheet; WCAG 2.2 AA in both themes.
- Documentation with a page per component, and every component in every state on one page,
docs/all.html- the fixture checked in real engines, with its Arabic twin (see Documentation). - Two agent skills in
skills/: building screens with REDZ.UI, and moving a project from Bootstrap 5.3 with a scan and a map of every Bootstrap class (see Skills). - Budgets held on every commit: the stylesheet at most 36 KB and the script 34 KB, gzipped - the two files a project serves.
One rule for every class:
- A component, its variant, its size and its parts are one class each:
btn btn-primary btn-sm,card-header. Bootstrap 5's names, and Bootstrap 6's where it moved onto the platform (dialog,drawer,menu,check,switch). - A utility is
property-value, with Bootstrap 5's names:d-flex,justify-content-between,text-muted. - A condition is a prefix:
md:cols-2,print:d-none. A width prefix measures the region the element sits in - a column, a dialog, a card - not the window; a page without the shell marks its own with the classregion. - The number is the value:
p-16is 16 px,rounded-8is 8 px,w-50is 50 %,cols-3is three columns. - The parent sets the grid:
grid md:cols-2 gap-16; a child spans withspan-fullorlg:span-8. - A state is an HTML attribute:
aria-current,aria-pressed,disabled,[open]. A button opens a dialog with the browser's own command:commandfor="edit" command="show-modal". - Tokens are
--redz-plus the same words:--redz-text-muted,--redz-space-16.
Bootstrap Icons 1.13.2, one SVG file per icon in icons/, under their own class
names. css/icons.min.css delivers each icon as a CSS mask over its file, so an icon takes the color of the text around it
and needs no font, no JavaScript and no build. They are a pack of their own, released apart under the tag icons-1.13.2:
a project keeps it in a folder of its own, and a new release of the library leaves it alone.
<link rel="stylesheet" href="redz-ui/css/redz-ui.min.css">
<link rel="stylesheet" href="redz-ui-icons/css/icons.min.css">
<i class="bi bi-pencil" aria-hidden="true"></i>Such an icon follows the system colors in forced-colors mode and prints, from your own server or from a CDN that sends
Access-Control-Allow-Origin on .svg: a browser fetches a mask in CORS mode, and without that header every icon is blank.
One file beside the stylesheet carries them all - js/redz-ui.min.js, the modules of js/index.js as one; each
attaches by delegation on document, so nothing is set up per element and markup added later works the same:
<script type="module" src="js/redz-ui.min.js"></script>
<script type="application/json" id="redz-ui-text">{"close": "Close", "confirm.ok": "OK", "confirm.cancel": "Cancel", "confirm.phrase": "Type “{phrase}” to confirm", "busy": "Loading…", "remove": "Remove {label}", "found.one": "{count} result", "found.other": "{count} results", "create": "Create “{label}”", "strength.short": "Too short", "strength.fair": "Fair", "strength.good": "Good", "strength.strong": "Strong", "moved": "Moved to {position} of {count}", "more": "More", "slide": "{position} of {count}", "file.type": "{name} is not a kind of file this takes", "file.size": "{name} is larger than {size}"}</script>- Menus (
role="menu"on apopoveropened by its button): the arrow keys move through the items, Home and End go to the ends, a letter to the next item it starts; Tab and Escape close the menu; the down and up arrows on its button open it on the first or the last item (js/menu.js). - Tabs (
role="tablist"): the arrow keys select the next tab and show its panel - mirrored right to left - Home and End the first and the last; only the selected tab takes Tab. Withdata-activation="manual"- panels that load from the server - the keys move the focus only, and Enter or Space selects. Before a person's choice selects a tab, the tablist fires the cancelableredz:select(tab,from), which a page answers to ask first (js/tabs.js). - Nav overflow (
nav-overflowaround a nav): the links that do not fit the wrapper's own box hide from the end into a "More" menu after the nav, the current link andnav-overflow-keepones staying in the row; a plain click on an item clicks the hidden link itself; a link that opens a menu folds as a group of its items. It watches the wrapper's size and the nav's links, and takes up a wrapper added later (js/nav-overflow.js). - Scrolling navs: a nav or a tab list wider than its box scrolls, the edge it goes on past fades (
data-more: start, end), and its current item is scrolled into its view when it loads (js/nav-scroll.js). - Navbars (
navbarwith anavbar-togglerand a<nav class="drawer" popover>): below itsnavbar-expandwidth the toggler opens the links as a panel, which closes when a link in it is followed, when the focus leaves it for the page and when the header grows wide enough for its row (js/navbar.js). - Regions: a button with
command="--toggle"opens and closes the element itsaria-controlsnames, closed ashidden="until-found", so the browser's find in page still reaches its words and opens it; every button for the region says the state inaria-expanded(js/toggle.js); a table's row closes as plainhidden. It takes nocommandfor, which WebKit would expose as collapsed whateveraria-expandedsays. An accordion needs no script: it is<details name>. - Carousels: the slides of a
carousel-innerscroll and snap by the browser; buttons withcommand="--prev"or"--next"andaria-controlsmove a slide round the ends, an emptycarousel-indicatorsfills with a button per slide, andcommand="--play"plays them while pressed - never for less motion, paused on hover and focus; a slide a person moves to is told politely (js/carousel.js). - The folded rail: a header button with
command="--fold"andaria-controlsnamingapp-sidebarfolds it into a rail of icons on a wide window and back (aria-pressed,data-folded);redz:foldtells the page, which keeps the choice (js/fold.js). - Notices:
REDZ.ui.toast({title, text, tone, action, persistent})shows a toast in the live regions of.toast-container-role="status",role="alert"fordanger- or in an open modal dialog's own. Success and info leave after 8 seconds unless the pointer or the focus rests on them; an error and a notice with an action stay until closed (js/toast.js). - Confirmation:
await REDZ.ui.confirm({title, text, note, sections, ok, cancel, tone, phrase})asks in a modal alert dialog and answerstrueorfalse; the focus starts on cancel. Anotein a tone andsections- a title over a list, such as what goes and what stays - say what a deletion takes with it, all of it text. With aphrasethe person types it first: the confirming button waits for it and the focus starts in its field (js/confirm.js). - Busy:
REDZ.ui.busy(element, promise)marks the elementaria-busyuntil the promise settles. A button is alsoaria-disabled, which keeps its focus and swallows its clicks - as on every button, check, radio or switch so marked; its leading icon turns into a spinner, anddata-busygives the words it shows and says while it waits. A region - a card, its body, a dialog's or a drawer's body, anyregion- turnsinertunder an overlay that shows after 400 ms and stays at least 400 ms; once it shows, one polite message says so, and a focus the region dropped comes back to the same element or its namesake in the new content (js/busy.js). - Dialogs:
closedby="any"closes a modal dialog by a click outside it; a modal dialog locks the page's scroll by CSS alone (js/dialog.jsstands in forclosedbywhere the browser lacks it). - Comboboxes (a search field
role="combobox"controlling alistbox): typing filters the list - case and accents aside, every word found, the best first - the arrow keys move the active option while the focus stays in the field, Enter takes it, and once the typing pauses that option is said politely, or the count when nothing is found. In a form, acombobox-togglebutton shows the value and opens the panel, and the value lives in hidden fields named by the list'sdata-name; a page that searches its server cancelsredz:searchand puts the options in. A list withdata-createoffers “Create …” for words no option has: the new label goes into the hidden fielddata-createnames, or the page makes it throughredz:create. The command palette is the same search in a<dialog>, its options links and buttons (js/combobox.js). - Inline edit: a button with
command="--edit"opens a form of classinline-edit-formin place of the value it names, or as a popover beside the button; Enter saves, Escape puts the field back. Its submit firesredz:save, which a page that saves answers with a promise: the Save button is busy, a refusal's words show as the field's error (js/inline-edit.js). - Passwords: a button with
command="--reveal"shows a password field's text and hides it again,aria-pressedsaying which; elements withdata-strengthnaming the field show its level as it is typed - by its length and its kinds of characters - a meter at once, the words after a pause (js/password.js). - Trees: a list of class
treeopens its groups by--togglebuttons; a group withdata-lazyasks the page for its items byredz:expandon its first opening, and--expand-all/--collapse-allopen and close a whole tree (js/tree.js). - Reordering: a list, a table's
tbody, a grid or a tree withdata-reordermoves its items by adrag-handle- a button opening the list's menu of moves (up, down, first, last; in and out in a tree) - or by the pointer dragging it, down a column or, in a grid or a row, before or after the item under the pointer; the move is said, thenredz:movelets the page save it or refuse it, which undoes it, and hidden fields hold the order (js/reorder.js). - PIN pads: a
pinaround one numeric password field draws its dots and a keypad whose keys never take the focus; when the PIN is whole it asks the page inredz:pinand is busy while the page checks, a wrong one cleared and marked; left alone it submits its form (js/pin.js). - Cooldowns: a button with
data-cooldown="N"waits N seconds,aria-disabled, the seconds left after its words, then wakes; setting the attribute again starts it again (js/cooldown.js). - Views: buttons with
command="--view",aria-controlsand avalueput the value in the list'sdata-view- tiles or rows - press the one that shows and tell the page inredz:view, which keeps the choice (js/view.js). - Highlights: elements that share a
data-highlightkey light together on hover and focus - a chart's lane and its row in a table (js/highlight.js). - Uploads: an
uploadtakes files chosen, dropped on itsupload-zoneor pasted, checks each againstacceptanddata-max-size, gives each a row from the page's<template>and sends each byredz:files, with its own progress and its own error; an input withoutmultipleholds one file, a new one taking the place of the last;upload-thumbsshows images as thumbnails,data-max-filescaps the count, and a page may answer with a prepared file that the form then sends (js/upload.js). - The page's answers: a moment only the page can carry out is a cancelable
redz:*event on the widget; the page that takes it callsevent.preventDefault()andevent.detail.respondWith(promise), and the widget waits for the promise (js/answer.js). - Shortcuts: a button or link whose
aria-keyshortcutsnames a combination with Control, Meta or Alt is pressed by it -Meta+K Control+Kanswers ⌘K on Apple's systems and Ctrl K elsewhere (js/shortcuts.js). - Scrollspy: in a nav or list group with
data-scrollspy, the link to the section in view says so inaria-current="location"(js/scrollspy.js). - Tooltips from titles: inside an element with
data-tooltips(<html>for the whole page) an element's owntitleshows as the library's tooltip after a short hover and on a keyboard's focus; Escape hides it, and the browser's own is held back meanwhile (js/tooltips.js).
The library has no words of its own: every word it shows comes from the caller or from the page's island
#redz-ui-text, and a missing one is named in the console.
A package of the release, linked apart: css/redz-ui-editor.min.css and the module js/redz-ui-editor.min.js,
built from editor/ as the main two are from css/ and js/. The module sets
REDZ.ui.editor(host, {value, ids, rights, placeholder, lang, dir, styles, scripts, sites, languages, source, facts, onFact, ai, onBatch, person, blocks, images, embed, grid, onChange, onReady}) and answers getHTML(), setHTML(html), setRights(rights), focus(), destroy(). It
edits REDZ.UI markup only: prose in place, and every other top element a block with a stable id, closed to the browser, its
words fields. What can be changed on an element comes from the catalog, tools/catalog.json, also the passport's
editor.catalog, which the build holds against the stylesheets, so nobody needs HTML; an expert may have it, under a
right. A price or a date is a live fact, <data value>, written once. The AI is the host's, options.ai, no key is ever in a
page: a suggestion is drawn word by word and taken or left, what it writes passes through the catalog, and a translation
stands beside its source, row by row, so only the block that changed goes stale. Rights only hide controls: the server decides on every save. The text lives in a frame whose own
Content-Security-Policy runs no script, and every value is parsed inert and cleaned, so nothing in a stored body or on
the clipboard runs. Its words are the island's editor.* texts, which the passport's editor.texts lists apart from the
library's; its soft ceilings are 70 KB of script and 8 KB of styles, gzipped.
Three more packages, linked on the screens that show them, listed in the passport's packages - each built into
dist/ from its folder, charts/, calendar/ and media/, which stay to read:
css/redz-ui-charts.min.cssand the modulejs/redz-ui-charts.min.js- a chart is the table of its numbers drawn by CSS: columns stacked by day (chart), a row's sparkline of runs (spark), a board of lanes over a time window (lanes). The numbers aredata-v,data-at,data-from,data-to,data-mediananddata-now, shares from 0 to 1, which the module hands to the stylesheet through the CSSOM - a strict CSP that refusesstyleattributes allows it - and a chart table keeps its table roles for screen readers.css/redz-ui-calendar.min.css- a month of days (calendar) and days across things (schedule); no script.css/redz-ui-media.min.css- a folder of images as tiles or rows (media-grid,media-tile); its view switch is--viewand its reorderingdata-reorder, both in the main script.
Their budgets, gzipped: charts 3 KB of styles and 2 KB of script, the calendar and the media library 2 KB each.
docs/ on the site: getting started, layout, utilities, tokens, icons, the move from Bootstrap 5.3, and a page per
component - examples whose code is printed from the same markup, every state as a named grid, keyboard and
accessibility, the tokens the component reads and its rows of the Bootstrap map. php tools/docs.php writes them from
their sources in docs/src/ inside one shell, with docs/all.html - every example and state of every page on one
page - the Arabic twins of that page and of the demo screens (docs/ar.tsv), a Markdown twin of every page, and
llms.txt with llms-full.txt; --check holds them to their sources on every commit. States the pointer or the keys
make - :hover, :focus-visible, :focus - are shown by the docs' own script, which copies those rules under
[data-docs-state]; the library's stylesheets carry no classes for the docs.
Two skills in the open Agent Skills format, released with the library:
skills/redz-ui- building screens: the rules, the page, and a reference per component;skills/redz-ui-from-bootstrap-5- moving a project from Bootstrap 5.3, file by file:scripts/scan.shreports every Bootstrap class,data-bs-*attribute andbootstrap.*call left with its REDZ.UI form, and every class REDZ.UI does not have; the agent rewrites each file whole and scans again until nothing is left.
Both carry assets/map.tsv - every class of Bootstrap 5.3.8 and its REDZ.UI form, written by php tools/map.php from
Bootstrap's own stylesheet and tools/map/rules.tsv - and assets/classes.txt, every class of the library. Install
them with npx skills add redz-tech/REDZ.UI, or copy the folders into .agents/skills/.
Bootstrap 6's floor: Chrome and Edge 130+, Firefox 132+, Safari 18+ (macOS and iOS) - the browsers of autumn 2024.
What must work rests on features of the floor. Invoker commands, anchor positioning and closedby are enhancements: the
library writes them, and its scripts stand in below them and where an engine gets them wrong - js/commands.js hands a
command to the browser's own popovertarget or calls the dialog, js/menu-place.js places a menu the engine did not
put beside its button, js/dialog.js closes a dialog by a click outside it. Nothing is written for older browsers.
The floor gate. php tools/floor.php check reads every stylesheet and page of the library and checks each
property, value, function, unit, selector, at-rule, media feature, element and attribute against MDN's
browser-compat-data at those versions - on every commit and in the Check
workflow here. A block is checked only for the browsers that can apply it: @media (forced-colors: active) and
@supports (…) narrow them. A vendor form beside a property covers the browsers that need it, as
-webkit-print-color-adjust does for Chrome 130-135. Every exception is in tools/floor/rules.json with its reason,
and so is every repair of an engine bug with the first versions that fixed it: when the floor passes those versions, the
gate asks for the exception or the repair to be removed. php tools/floor.php check your.css page.html checks your own
files the same way.
floor.json lists every name the gate knows - property:print-color-adjust, value:display:grid, unit:dvh,
selector:has, media:forced-colors, attribute:button:commandfor - with "ok" or, per browser, what the floor
lacks. Its data is a pinned version of browser-compat-data in tools/floor/bcd.tsv, fetched and checked against its
sha512 by php tools/floor.php refresh.
Checks in real engines. php tools/probe.php opens the all-components page, the demo screens and the landing in
the installed Chrome, Firefox and Safari, in the floor's Chrome 130, Firefox 132 and Safari 18 in the iOS simulator, and
in Chrome 136 and Safari 26 in the simulator, whose bugs js/menu-place.js repairs - at 1440, 1024, 390 and 320 px,
light left-to-right and dark right-to-left, the right-to-left cells on the pages' Arabic twins. It looks for overflow,
icons without a mask, width prefixes outside a region, cut labels, aria-busy on live regions and errors; opens every
menu and dialog and checks where it lands; walks every menu, combobox, modal dialog and tab list by real keys - and a
dialog with closedby by a click outside it - against the WAI-ARIA patterns, and sends two notices; holds the docs' copies of
:hover and :focus-visible against the real pointer and Tab; has Chrome name the fonts of the Arabic text; runs
axe-core; checks forced colors and print where the engine has them; and runs the minifier's scripts of tools/min/ as
written and minified, and the release's stylesheet as written and squeezed, which must agree. Every docs page runs in the current Chrome,
Firefox and Safari at 1440 and 390 px. Older engines are fetched once into ~/Library/Caches/redz-ui and checked
against their checksums. The screen readers go through the speech steps of every widget that speaks, against the words
each step must hold: voiceover hears VoiceOver in Safari; nvda-chrome and nvda-firefox hear NVDA 2026.3 in Chrome
and Firefox on Windows - a portable copy made from its official installer, its speech and keys through NVDA's own
Remote Access, so it meets a page in browse mode as a user does - and NVDA also reads each of those pages whole by its
quick navigation (headings, landmarks, form fields) against what the page holds. The sheet of shots and findings is
written to .dev/probes/; the Probes workflow runs it on every commit in nine parts - joined by php tools/probe.php merge - on macOS 15, VoiceOver also on macOS 26, NVDA on Windows Server 2025, with a pinned Chrome for Testing build as
the current Chrome, as the images' own lag; every release carries its sheet, and the site shows the latest under checks/. On a Mac the
engines run at once, a process each (the engines safaridriver drives, Safari, VoiceOver and the simulators, one after
another; --serial runs them all so), and --pages=docs/menu.html,… checks only those pages, as the workflow's pages input does for a lab
run. CI checks only what changed since the nearest commit that passed (php tools/probe.php affected BASE says what and
why: nothing for texts and tools, the changed pages alone, or everything), a commit that passed runs nothing again, and a run whose job GitHub
never placed on a runner runs its failed jobs once more by itself.
Every release is an immutable GitHub release: once it is out, neither
its tag nor its files can change, and GitHub signs an attestation of both. A tag v* becomes a release only when the
probes are clean in every engine; the release carries two files:
redz-ui-<version>.zip-dist/of the tag, the folder a project serves as it is:css/andjs/with minified files only, the license files and the passportredz-ui.json: the version, the floor, the files to link, every class, custom property, command and text key of the library, and the sha256 of every file in the SRI form.php tools/build.phpwrites the folder, and--checkholds it current on every commit. The sources, the docs andfloor.jsonstay in the repository and in the tag's source archive;checks.zip- the sheet of the probes the release passed; the site shows the latest release's underchecks/.
The icons are a release of their own, under a tag icons-<version> that follows Bootstrap Icons: redz-ui-icons-<version>.zip
holds css/icons.min.css, icons/ and the pack's passport redz-ui-icons.json. The library's passport names in icons
the version it needs. php tools/build.php --check refuses icons that changed under a published version.
gh release download v0.18.2 --repo redz-tech/REDZ.UI --pattern 'redz-ui-*.zip'
gh release verify-asset v0.18.2 redz-ui-0.18.2.zip --repo redz-tech/REDZ.UI
gh release download icons-1.13.2 --repo redz-tech/REDZ.UI --pattern 'redz-ui-icons-*.zip'
gh release verify-asset icons-1.13.2 redz-ui-icons-1.13.2.zip --repo redz-tech/REDZ.UIjsDelivr serves a release by its tag: https://cdn.jsdelivr.net/gh/redz-tech/REDZ.UI@v0.18.2/dist/css/redz-ui.min.css
and https://cdn.jsdelivr.net/gh/redz-tech/REDZ.UI@icons-1.13.2/css/icons.min.css. Versions
follow Semantic Versioning: a class, token, attribute or JavaScript call removed or renamed is a major release, one
added is a minor release, a fix is a patch; while the version is 0.x, a removal or a rename is a minor release.
The library is under MIT, see LICENSE. The editor is under PolyForm Strict 1.0.0 from 0.17.0: noncommercial use only, no changes, no redistribution; versions up to 0.16.0 stay under MIT. See LICENSING.md. Pieces taken from other MIT-licensed projects are listed with their notices in NOTICE.