zgui
Component library

The component library

The four optional crates that ship beside zgui, how they relate, what an application gives up by leaving them out, and the whole component inventory.

zgui ships a component library: buttons, fields, menus, dialogs, tables and the rest. No page of this documentation before this one uses it, and no application has to. This page says what the four crates are, shows that they are ordinary consumers of the public API, and writes the same small interface twice — once with the library and once from the element vocabulary — so that what the library buys is visible.

It assumes the guide.

The four crates

CrateDepends onWhat it adds
zgui-ui-tokenszguidesign tokens as CSS custom properties, and ThemeProvider
zgui-ui-iconszgui22 icon constants and one Icon component
zgui-ui-primitiveszguieight interaction behaviours that draw nothing
zgui-uizgui, and the three abovethe styled components

The first three know nothing about each other. zgui-ui is the only crate that joins them: a component of its own is a primitive composition, plus a sheet written in tokens, plus an icon where one is wanted.

None of the four is reachable through zgui. Each is a separate line in a Cargo.toml, and a program that names none of them compiles none of them.

Layer L8, and no privileged access

Architecture overview puts these crates on L8, the product layer: crates that consume the public API and are consumed by nothing. For these four the claim is checked rather than asserted. Here are the whole dependency sections of the four manifests:

crates/zgui-ui-tokens/Cargo.toml, -icons, -primitives
[dependencies]
zgui = { path = "../zgui", version = "0.1.0" }
crates/zgui-ui/Cargo.toml
[dependencies]
zgui = { path = "../zgui", version = "0.1.0" }
zgui-ui-icons = { path = "../zgui-ui-icons", version = "0.1.0" }
zgui-ui-primitives = { path = "../zgui-ui-primitives", version = "0.1.0" }
zgui-ui-tokens = { path = "../zgui-ui-tokens", version = "0.1.0" }

Three tests hold that shape in place:

TestWhat it readsWhat it refuses
zgui-ui/tests/downstream.rs::the_library_depends_on_the_umbrella_and_on_its_own_siblings_and_on_nothing_elseits own Cargo.tomla dependency outside the four names above
zgui-ui/tests/downstream.rs::no_component_names_a_crate_below_the_public_apievery .rs file under srcthe text zgui_dom::, zgui_style::, zgui_layout::, zgui_paint:: and eleven more
zgui-ui-primitives/tests/downstream.rs::the_sources_name_no_crate_below_the_public_apithe same, for the primitivesthe same list

zgui-ui-tokens carries the second test too. zgui-ui-icons is covered by its manifest alone; its sources name no internal crate either.

The crate documentation of zgui-ui gives the reason, and calls it a constraint rather than a boast:

if a component needed something the framework does not expose, an application would need it too, so the hole gets fixed rather than reached past.

This is why the guide could be written without the library. Every mechanism a component here uses — css!, style!, variants!, install_stylesheet, #[prop(attrs)], a11y:, state:, Portal, NodeRef — has a page above, because a component that needed anything else would be a component you could not have written.

The dependency rule covers [dependencies] only. The dev-dependencies of zgui-ui do reach lower — zgui-platform-headless, zgui-render-wgpu and a vector rasteriser — because the library's own tests read pixels back off a real graphics device. Nothing an application links carries them.

What you give up by leaving it out

Work, not capability. There is no behaviour in the library that the public API does not also give an application, because the library has nothing else to build from.

The library wroteWithout it you write
a sheet per component, in design tokensa css! or style! sheet of your own
a variants! table per visual axisyour own table, or class strings
the keyboard map for every composite controlyour own on:key_down handlers
roles, labels and relations on every elementyour own a11y: attributes
overlay composition: portal, exit animation, dismissal, focus containment, placementthe same four primitives, in the same order
one protocol for who owns a valueyour own signal-or-callback prop pair

The evidence that this is a real choice is in the repository. crates/zgui-examples depends on zgui and nothing else, and its manifest says why:

These are ordinary applications, and an application depends on the umbrella crate and nothing else: if a view, a component, a style sheet or an entry point needed anything further, the examples here would not compile, which is the point.

Six worked applications sit under examples/counter.rs, todo.rs, styled.rs, tally.rs, resize.rs and vector.rs — and none of them names zgui-ui.

How to depend on it

Add the crates you will name. zgui-ui pulls its three siblings in for its own use, but a crate you write use for is a crate you declare.

Cargo.toml
[dependencies]
zgui = { path = "../zgui/crates/zgui" }
zgui-ui = { path = "../zgui/crates/zgui-ui" }
zgui-ui-tokens = { path = "../zgui/crates/zgui-ui-tokens" }
zgui-ui-icons = { path = "../zgui/crates/zgui-ui-icons" }   # only if you name an icon yourself

Import the preludes. Each crate has one, and each holds every component together with the props type its #[component] attribute generated.

use zgui::prelude::*;
use zgui_ui::prelude::*;
use zgui_ui_tokens::prelude::*;

zgui_ui::prelude re-exports one name from the headless half: Binding, which is what a value, checked or open prop takes. The *Style types are deliberately absent, and so are the *Variants structs; name them at their own path when you override one.

Render a ThemeProvider around the interface. zgui-ui never installs the token sheet itself. Every rule it ships is written in var(--zui-…), so without a provider — or without custom properties you define yourself — those declarations resolve to nothing.

view! {
    ThemeProvider(scheme = ColorScheme::System) {
        Card {
            CardHeader {CardTitle {"Settings"}}
            CardContent {Button {"Save"}}
        }
    }
}

The icon constants are not in zgui_ui_icons::prelude. Name the ones you draw: use zgui_ui_icons::set::{chevron::CHEVRON_RIGHT, mark::CHECK};

Three ways to use the stack

WayDependenciesYou writeYou get
everythingzgui, zgui-ui, zgui-ui-tokenscomponent callsappearance, keyboard and accessibility, decided
behaviour onlyzgui, zgui-ui-primitivesevery style sheet, every elementplacement, focus containment, dismissal, exit timing, roving focus, value ownership
nonezguiall of itthe sixteen element names and the whole public API

The middle row is a real position, not a compromise. zgui-ui-primitives depends on zgui alone, ships its own prelude, and carries a complete popover composition as a doctest. Primitives works through it.

Tokens and icons are separable in the same way. zgui-ui-tokens plus css! is a design system with no zgui-ui in the dependency graph, and Icon is one component over the vector element.

Three rules every styled component obeys

Appearance is a variants! table and a style! sheet. The table lowers to a class list and one data- attribute per axis; the sheet selects on the attributes. Nothing computes a class name at run time, and nothing branches on a variant in Rust.

Interaction state belongs to the engine. There is no hovered, focused or pressed signal anywhere in zgui-ui. A test refuses one by name. :hover, :focus-visible, :active, :disabled, :checked, :indeterminate, :placeholder-shown and :invalid are states the style engine already knows, and a second copy would be a second answer that disagrees on the frame the pointer leaves.

A caller can always add to an element. Every component that renders an element takes class: Classes and #[prop(attrs)] attrs: Attrs, and merges both after its own, so the caller wins. A test walks the sources and fails any component that renders an element and forwards no bundle.

The inventory

Every public component of zgui-ui, by module. Props are on Components.

ModuleWhat it isComponents
buttona control that does something when pressedButton
button_groupbuttons that belong together, drawn as one stripButtonGroup, ButtonGroupSeparator, ButtonGroupText
badgea small piece of status textBadge
labeltext that names a controlLabel
kbdthe name of a key, set as a keyKbd, KbdGroup
separatora rule between two runs of contentSeparator
skeletonthe shape of content that has not arrivedSkeleton
spinnera mark that turns while something is happeningSpinner
avatara picture of a person, with a fallbackAvatar
aspect_ratioa box that keeps its width and height in proportionAspectRatio
alerta message about the whole viewAlert, AlertTitle, AlertDescription
carda surface holding one subjectCard, CardHeader, CardTitle, CardDescription, CardContent, CardFooter
emptywhat a region says when there is nothing in it yetEmpty, EmptyHeader, EmptyMedia, EmptyTitle, EmptyDescription, EmptyContent
itemone row of a list: a mark, some words, the controlsItem, ItemGroup, ItemMedia, ItemHeader, ItemContent, ItemTitle, ItemDescription, ItemActions, ItemFooter, ItemSeparator
progresshow far along something isProgress
inputone line the user typesInput
textareaseveral lines the user typesTextarea
input_groupa text field with marks, words or controls attachedInputGroup, InputGroupAddon, InputGroupButton, InputGroupInput, InputGroupText, InputGroupTextarea
input_otpa code entered one character per boxInputOtp
fieldone thing being asked for, with everything needed to answer itField, FieldSet, FieldLegend, FieldGroup, FieldLabel, FieldTitle, FieldContent, FieldDescription, FieldError, FieldSeparator
checkboxyes, no, or mixedCheckbox
radio_groupone of severalRadioGroup, RadioGroupItem
switchon or off, applied at onceSwitch
togglea control that stays pressedToggle
toggle_groupseveral toggles, one or many pressedToggleGroup, ToggleGroupItem
slidera number chosen along a trackSlider
collapsibleone region that opensCollapsible, CollapsibleTrigger, CollapsibleContent
accordionseveral regions, one or many openAccordion, AccordionItem, AccordionTrigger, AccordionContent
tabsone panel of severalTabs, TabsList, TabsTrigger, TabsContent
menubara bar of menus along the topMenubar, MenubarMenu, MenubarTrigger, MenubarContent, MenubarItem, MenubarLabel, MenubarSeparator, MenubarArrows
navigation_menusections that open a panelNavigationMenu, NavigationMenuList, NavigationMenuItem, NavigationMenuTrigger, NavigationMenuContent, NavigationMenuLink
breadcrumbwhere you are, and the way backBreadcrumb, BreadcrumbList, BreadcrumbItem, BreadcrumbLink, BreadcrumbPage, BreadcrumbSeparator, BreadcrumbEllipsis
paginationone page of manyPagination, PaginationContent, PaginationItem, PaginationLink, PaginationPrevious, PaginationNext, PaginationEllipsis
sidebara collapsing side panelSidebarProvider, Sidebar, SidebarHeader, SidebarContent, SidebarFooter, SidebarGroup, SidebarGroupLabel, SidebarInset, SidebarMenu, SidebarMenuItem, SidebarMenuButton, SidebarTrigger
scroll_areaa scrolling region with its own barsScrollArea, ScrollBar
resizablepanels the user drags apartResizablePanelGroup, ResizablePanel, ResizableHandle
carouselslides, one at a timeCarousel, CarouselContent, CarouselItem, CarouselPrevious, CarouselNext
toastwhat the application says after the factToaster, ToastItem
formfields, what is wrong with them, and sending themForm, FormField, FormItem, FormLabel, FormDescription, FormMessage, FormSubmit
dialoga modal surface over the windowDialog, DialogTrigger, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogFooter, DialogClose, DialogDismiss
alert_dialoga dialog that demands an answerAlertDialog, AlertDialogTrigger, AlertDialogContent, AlertDialogAction, AlertDialogCancel; title, description, header and footer are the dialog's, aliased
sheeta modal panel from one edgeSheet, SheetContent; the rest are the dialog's, aliased
drawera sheet with a drag handleDrawer, DrawerContent, DrawerHandle; the rest are the dialog's, aliased
popovera surface anchored to a controlPopover, PopoverContent; trigger and close are the dialog's, aliased
tooltipa short note on hover or focusTooltip, TooltipTrigger, TooltipContent
hover_carda richer note, on hoverHoverCard, HoverCardTrigger, HoverCardContent
menuthe parts every menu is made ofMenuContent, MenuItem, MenuGroup, MenuLabel, MenuSeparator, MenuShortcut, MenuCheckboxItem, MenuRadioGroup, MenuRadioItem, MenuSub, MenuSubTrigger, MenuSubContent, MenuTypeahead
dropdown_menua menu under a buttonDropdownMenu, DropdownMenuTrigger, DropdownMenuContent, then the Menu… parts
context_menua menu at the pointerContextMenu, ContextMenuTrigger, ContextMenuContent, then the Menu… parts
selectone option chosen from a listSelect, SelectTrigger, SelectValue, SelectContent, SelectItem; group, label and separator are the menu's, aliased
comboboxthe same, with typing to narrow itCombobox, ComboboxInput, ComboboxContent, ComboboxItem, ComboboxEmpty
native_selecta chooser with no surface of its ownNativeSelect, NativeSelectOptGroup, NativeSelectOption
commanda command palette over the comboboxCommand, CommandList, CommandGroup, CommandDialog; input, item and empty are the combobox's, aliased
tablerows and columns, presentationalTable, TableCaption, TableHeader, TableBody, TableFooter, TableRow, TableHead, TableCell
data_tablea table driven by a model: sort, filter, page, selectDataTable, ColumnResizer
virtualizeonly the rows in the viewportVirtualList
chartbars, lines and areasChart
calendara month of daysCalendar, CalendarDay
date_pickera calendar in a popoverDatePicker
overlaywhat the twelve floating and modal components are built fromModalSurface, AnchoredSurface, OverlaySurface, Scrim, Confined, Elevated
listboxthe registry Select, Combobox and Command shareListboxCatalogueOf
supportwhat a component of your own needs toovariant_attrs and the numeric and editing helpers

overlay, listbox and support are absent from the prelude. Name them at their module path — zgui_ui::overlay, zgui_ui::listbox, zgui_ui::support. They are published because a surface of your own needs them.

The same interface, twice

Two buttons in a row. One is outlined, the other goes disabled while a save is in flight.

use zgui::prelude::*;
use zgui::reactive::RwSignal;
use zgui::{component, view};
use zgui_ui::prelude::*;
use zgui_ui_tokens::prelude::*;

#[component]
fn SaveBar() -> impl IntoView {
    let saving = RwSignal::new_local(false);

    view! {
        ThemeProvider(scheme = ColorScheme::System) {
            row {
                Button(variant = ButtonVariant::Outline) {"Cancel"}
                Button(disabled = saving, on:click = move |_| saving.set(true)) {"Save"}
            }
        }
    }
}

The second version is a working button. Enter and Space press it, because activating what has focus is the framework's behaviour and arrives as an ordinary click. disabled takes it out of the focus order, out of pointer reach, and tells a reader — one write, four consumers. zgui-ui's own Button handles none of those things either, for the same reasons.

What the library's Button adds to it:

Button also hasWhich is
six variants and four sizesa variants! table, plus rules keyed on data-variant and data-size
colours that follow the colour schemevar(--zui-color-primary) and its neighbours instead of the literals above
a sheet installed under a nameinstall_stylesheet("zui-button", ButtonStyle::CSS) in the body, so the sheet arrives with the first button and keeps its place in the cascade

That is the whole difference for a button. For a dialog or a select the difference is larger — a portal, an exit animation, a dismissal layer, a focus trap and a placement solver, in that order — but it is the same kind of difference. Components takes three of them apart.

What it costs

Nothing structural. A component is a function that installs a sheet and returns elements, so a frame that touches one costs what the cost model says the change costs, and no more.

Two consequences worth stating:

  • One sheet per component type, however many instances. Each component calls install_stylesheet with a stable name in its body. Installing a name that is already installed replaces the text and keeps its place in the cascade, so a hundred buttons install one sheet. Asserted by crates/zgui-ui-tokens/tests/theme.rs::a_second_instance_of_a_component_does_not_install_a_second_sheet.
  • A pointer crossing a component is a restyle, not a rebuild. Because no component keeps an interaction state in a signal, hovering one is the two-element restyle the cost model measures, and nothing in Rust runs at all.

Next

On this page