Skip to content

Primitives

P · Primitives

One concern each

A primitive depends on nothing but tokens, owns exactly one concern and is never domain-aware. Eleven have a visual contract and are shown here; three (VirtualScroller, DragSource, useCombobox) are behavioral kernels whose output is consumed by List, Table and Select.

Box

P-01 Element page #428

The layout host: every rectangle in the app. Absorbs the two thirds of inline styles that are pure layout. A Box has no color of its own.

Axis Values
data-display flex · inline-flex · grid · inline · none
data-direction row · column · row-reverse
data-gap · data-p · data-px · data-py 0 · 1 · 2 · 3 · 4 · 6 · 8 (token scale)
data-align · data-justify start · center · end · stretch · baseline / start · center · end · between · around
data-wrap · data-flex · data-w · data-h · data-overflow · data-position · data-cols wrap / 1 · auto · none / full / hidden · auto / relative · absolute · sticky / 2 · 3 · 4 · auto
Flex row, gap 2, space-between
one
two
three
Grid, 3 columns, gap 3
1
2
3
4
5
6

Text

P-02 Element page #429

All typography, headings included. No attributes gives sm / regular / ink. Layout-only styling (overflow, whitespace) is the only thing that goes in style.

Axis Values
data-size xs · sm · md · lg · xl · h1 · h2 · h3 · h4 · h5 · h6 · overline
data-weight light · regular · medium · semibold · bold · heavy
data-tone secondary · muted · disabled · link · inverse
data-color primary · info · success · warning · error · purple · electric-blue · pink · yellow · brown
data-align · data-truncate · data-mono center · end / (1) · 2 · 3 lines / monospace
Tones and colors

Default: 14px, weight 500, ink.

Secondary tone for supporting copy.

Muted tone for metadata and captions.

Disabled tone.

Success color, medium weight.

Error color, medium weight.

Purple accent.

Truncated: this sentence is far too long for the width of its container and will be clipped with an ellipsis.

Two-line clamp: this sentence is far too long for the width of its container and will be clipped after the second line, with an ellipsis at the end of the second line.

monospace · 4f3c-91ab-…

Icon

P-03 Element page #430

One SVG glyph from the sprite. Strictly non-interactive (pointer-events: none): wrap it in a Pressable or Button to make it clickable. Default 20px, 24px on mobile. Never size the inner glyph; size the wrapper.

Glyph families. Tabler (sprites/icons.svg) everywhere. Editor exception: the QEditor editors use Fluent UI System Icons, Regular style only (sprites/office-icons.svg), inside .qf-office and qf-office__* elements and in the office catalog sections. The two families never mix, and office symbols carry stroke="none" so this primitive's stroke never outlines them. Both rules are gated; see the README's "Icons" section.

Axis Values
data-size inherit · xs (14) · sm (16) · md (20) · lg (24) · xl (32)
data-color inherit · primary · info · muted · success · warning · error · purple · electric-blue · pink · yellow · brown · inverse
data-spin · data-pulse loading animations
Sizes
inherit →
Colors and motion

Surface

P-04 Element page #431

A bounded visual container: background, border, radius, elevation and the shared hover/selected pattern that cards, list rows and kanban cards all use.

Axis Values
data-tone (paper) · raised · sunken · primary · info · success · warning · error · inverse
data-radius none · sm · md · lg · xl
data-elevation xs · sm · md · lg · xl · 2xl
data-bordered · data-interactive 1px token border / hover + selected + focus states
aria-selected · data-state true / hover · selected
Tones
paper
raised · md
sunken
primary
success
warning
error
inverse
Interactive: rest · hover · selected
Rest (hover me)
Hover
Selected

Pressable

P-05 Element page #432

The single interaction target: focus ring, keyboard activation, hover/press/selected state, disabled semantics. Button, Chip, Tab, Menu.Item and list rows are all Pressables. Use a real <button> or <a>.

Axis Values
data-shape circle
data-icon-only · data-size square hit target / xs · sm · md
data-block full width, start-aligned (list rows)
aria-pressed · aria-selected · :disabled state
States (tab through to see the focus ring)

Layer

P-06 Element page #433

Overlay positioning, scrim, focus containment and dismissal: the one primitive behind every floating surface. In static HTML the platform provides most of it: <dialog> for modal/drawer, popover for popover/menu/tooltip. The class exists for hosts that need manual stacking.

Axis Values
data-kind modal · drawer · popover · tooltip · toast
data-placement left · right · bottom (drawer)
data-state · data-depth open · closed / 0 · 1 · 2 · 3 (stack offset, +10 z each)
Modal layer with scrim (rendered inline for the specimen)
A panel inside the layer

Transition

P-07 Element page #434

Enter/exit presets driven by data-state, plus keyframe classes for elements that mount already open. Honors prefers-reduced-motion globally.

Axis Values
data-preset fade · scale · slide-up · slide-down · slide-left · slide-right · collapse
data-state · data-speed open · closed / fast · slow
.qf-enter-* one-shot mount animations with the same names
Hover each tile to replay its preset
fade
scale
slide-up
slide-down
slide-left

ScrollArea

P-08 Element page #435

A scroll container whose edge shadows appear only when there is more content in that direction. Pure CSS: the shadows are background layers with background-attachment: local, so no scroll listener is needed. Replaces ScrollShadows.

Axis Values
data-axis y (default) · x
data-hide-scrollbar hide the bar, keep the shadows
Vertical and horizontal
  • Row 1
  • Row 2
  • Row 3
  • Row 4
  • Row 5
  • Row 6
  • Row 7
  • Row 8
InvoicesContractsPurchase ordersReceiptsStatementsCorrespondence

Divider

P-09 Element page #436
Axis Values
data-orientation horizontal (default) · vertical
data-tone · data-inset · data-label subtle · strong / inset margins / labeled rule
Variants



Left
Right

Skeleton

P-10 Element page #437
Axis Values
data-shape text · rect · circle
style width/height geometry only; always aria-hidden
A loading list row and card

Control

P-13 Element page #440

The bare focusable form element with no chrome. Field (C-09) supplies the label, border and helper shell; Control is the <input>, <textarea> or <select> inside it. Shown here without a Field to make the point.

Axis Values
type · placeholder · inputmode · autocomplete native attributes pass through
data-align · data-mono end (numeric) / monospace
:disabled · [readonly] native state
Bare controls (no Field)

Behavioral primitives

P-11 · P-12 · P-14 · P-15–P-20 Element page

These own behavior, not pixels. Their visual output is whatever the consuming component renders; the contract below is what the static layer already provides for them. P-15 to P-20 serve the QEditor office surfaces (office mockups); their demo behavior lives in js/office.js.

Primitive Contract Static hooks provided
VirtualScroller
P-11 · #438
Windowed rendering of long homogeneous collections. itemCount, itemSize (fixed or measured), overscan, scrollToIndex. Nothing visual. Renders inside a .qf-scroll; rows are ordinary .qf-list__item / <tr> positioned by transform.
DragSource
P-12 · #439
Pointer/keyboard drag kernel: move, sort, resize. Ships first as an adapter over the retained @dnd-kit. data-state="dragging" on the source, data-state="over" on the target, data-state="overlay" on the clone, and the handle classes .qf-list__handle, .qf-resizable__handle, .qf-table__resize. In the nav's organize mode (composites/nav-organize.css): .qf-nav__item or .qf-nav__row takes data-state="dragging" and "overlay" and is its own handle, a .qf-nav__group takes "dragging" as a whole, .qf-nav__row or .qf-nav__subitems takes data-state="over", and .qf-nav__drop-indicator marks the drop line. Every dragged outcome also has a keyboard and menu path (WCAG 2.5.7). The rest of the nav tree's hooks are below.
useCombobox
P-14 · #441 · gate for Select
Headless combobox/listbox: virtual focus, typeahead, ARIA wiring. Must merge before any Select variant is touched. aria-expanded on the trigger, role="listbox" + .qf-listbox__option[data-state="active"] for virtual focus, aria-selected for selection, aria-multiselectable for multi.
RovingFocus
P-15 · office
One tab stop per composite widget; arrow keys move, Home/End jump, optional wrap, 2-D mode for grids; skips hidden items and yields Left/Right to a text input with a caret (QEditor spec §4.1). [data-rove] on the container (a role="toolbar", tablist, listbox or grid); tabindex="0" on the current item and -1 on the rest. Ctrl+←/→ jumps to the next .qf-ribbon__group. Demo: js/office.js.
KeyTips
P-16 · office
Access-key layers: Alt+Windows (Windows) or Ctrl+Option (macOS) shows badges on File and the tabs; a letter selects a tab and shows that panel's badges; typing filters, Esc steps back one layer. data-keytip="H" on the target, rendered as .qf-keytip (C-31, aria-hidden; data-state="dimmed" for non-matching tips) and mirrored as aria-keyshortcuts on the control.
TextInputProxy
P-17 · office
The element that holds focus while editing a canvas: EditContext where available, else a hidden <textarea> moved to the caret for IME; routes keys, composition and clipboard to the engine (spec §4.5). Nothing visual. Carries aria-label ("Editing cell G13", "Slide 3 title placeholder") and aria-describedby → the mirror node; the mockups stand in with a role="textbox" on the edited content.
AccessibleMirror
P-18 · office
Off-screen semantic DOM for canvas content near the viewport and caret: headings, paragraphs, tables, lists, slide groups, grid cells, PDF text and annotations, kept in sync with the engine. role="document" with aria-roledescription="page" per page, role="grid" with aria-rowcount/aria-colcount for sheets, role="group" aria-roledescription="slide" for slides. The mockups' canvases are written in this form.
LiveAnnouncer
P-19 · office
One polite and one assertive region per app, with de-duplication and a one-second coalescing window ("Bold on", "3 of 27", "Connection lost. Your changes are kept."). .qf-office-app__live[role="status"] and .qf-office-app__live[role="alert"], visually hidden, inside .qf-office-app.
AdaptiveCollapse
P-20 · office
ResizeObserver-driven fitting of the ribbon (Compact and Full alike), status bar and tab row: as the width shrinks it sheds labels (icon-only, the name stays in aria-label and the tooltip), narrows wide comboboxes, moves controls into the trailing ⋯ menu by priority (last group first), then folds whole groups into dropdowns. Groups never overlap or clip; horizontal scrolling is the last resort. data-priority="1..4" on controls, data-collapsed="label small narrow hidden group" tokens written by the primitive, .qf-ribbon__more and .qf-ribbon__collapsed-group as the overflow targets; the container queries on .qf-office-app cover the widths of spec §1.13.

Nav tree hooks

The sidebar tree in the shell composite (composites/nav-tree.css and nav-organize.css). The shell composite shows a nested tree with its row menus.

Hook Item Markup and meaning
.qf-nav__subitems + style="--qf-nav-depth: N" D-1 A section of the tree. N is 1 under an entry and one more per group level; everything inside indents by N × --qf-nav-indent-step. It holds rows, groups, bare items, skeletons or an empty line.
.qf-nav__row in a section D-1, D-3 A search: a.qf-nav__item (aria-keyshortcuts="Shift+F10") and its row menu, a .qf-nav__action with aria-haspopup="menu", aria-controls and aria-expanded. The trigger keeps its hover state while expanded.
.qf-nav__group D-2 role="group", labelled by its item. Holds a .qf-nav__row whose button.qf-nav__item starts with a .qf-nav__chevron icon and spans the twisty and label columns, then the group's .qf-nav__subitems. Outside organize mode the button toggles the group (aria-expanded, aria-controls the subitems) and the chevron follows it; the layout follows the structure, not the attribute.
Right-click, Shift+F10, context-menu key D-3 Behaviour, in the binding: open the row's .qf-nav__action menu. js/docs.js demonstrates it on the shell composite.
data-hidden, .qf-nav__marker D-4 On a .qf-nav__row, .qf-nav__group or bare .qf-nav__item. Not shown outside organize mode (the app leaves it out). In organize mode the item is italic in --qf-nav-hidden-ink and ends with <span class="qf-nav__marker">(Hidden)</span>.
.qf-nav__add D-9 On an entry's .qf-nav__row, between the item and its .qf-nav__action, when the user may add to that entry: a button labelled “New in <entry>” holding the circle-plus-filled icon. The row gains an add column. Shown while the row is hovered or holds focus, always without hover, never in organize mode or the collapsed rail. The row menu offers the same action as “New…”.
.qf-nav[data-mode="organize"] D-5 Organize mode. The mode control is a button.qf-nav__item[aria-pressed]; rows are buttons that open their menu; rows in a section take the grab cursor (the row is its own handle), entry rows do not. Every group is open, so a group row's button is a menu button like the other rows (aria-haspopup="menu", aria-controls the row menu, aria-expanded the menu's state), and the chevron shows the group open from the mode.
.qf-nav__footer D-5 Classic. The rail's own controls (the Organize mode control, then Maintain in organize mode) as .qf-nav__item buttons in a .qf-nav__footer placed after .qf-nav__items in the .qf-nav. It stays pinned to the bottom of the rail; the rows scroll above it and pass under its edge, so it never covers a row or a focused control. An empty footer takes no room.
.qf-nav__skeleton, aria-busy="true", .qf-nav__twisty:disabled D-8 Loading: skeleton rows (aria-hidden, each a .qf-skeleton[data-shape="text"]) in a busy .qf-nav__subitems or .qf-nav__items; an entry's twisty is disabled while its searches load.
.qf-nav__empty D-8 A <p> in a section with nothing in it, e.g. "No saved searches".