Skip to content

Alacris UI

Alacris UI is sixty-eight Material Design 3 components, a three-tier token system, and a theme engine, vendored in this module. Material is the default; override the tokens to change the brand colour, or to leave Material behind.

mux.Handle("/_alacris/", alacris.RuntimeHandler())
<head>
@ui.Pending()
@alacris.Scripts(alacris.Config{
UI: true,
Version: buildRevision,
})
</head>

The JavaScript is vendored next to the alacris runtime. A Go project still needs no npm. The typed wrappers live in github.com/bmartel/alacris-go/ui.

  1. Adds @alacris/core, @alacris/ui, and the theme / motion / tokens specifiers to the import map. @alacris/core points at the same bytes as alacris, so the page has one reactive graph.
  2. Loads every component in the catalog and calls applyTheme with Material defaults (seed #e8ad18, Google Sans Flex, light/dark from the OS).
  3. Leaves your own Modules to run afterwards, so app components and the design system share the runtime.

Omit Modules when the page only uses Alacris UI. Keep them when you also define() your own elements.

Hide the tags until they upgrade:

@ui.Pending()

ui.Pending() is alacris.Pending{Tags: ui.Tags}: every Alacris UI tag. Without it the page paints empty custom elements, then reflows when the module arrives.

import "github.com/bmartel/alacris-go/ui"
A Material button from Go
return ui.Button(ui.ButtonProps{Variant: "tonal"}).Text("Save")
renders verified by go test
<ui-button variant="tonal">Save</ui-button>

Every Alacris UI tag has a typed wrapper in github.com/bmartel/alacris-go/ui.

The wrappers are generated from the vendored sources. A renamed prop, a new slot, or a theming token: regenerate and the Go side moves with it. Do not edit ui/*_gen.go.

If you also generate wrappers for your components, put those in a different package (./internal/components, ./webui) so the names do not collide. The design system is github.com/bmartel/alacris-go/ui. The example app generates into examples/todo/ui and aliases the design system as m3. That is a fixture layout, not the one to copy.

Every tag in ui.Tags has a matching ui.Foo constructor, ui.FooProps, ui.FooVars when the component declares a theming contract, event constants when it fires, and ui.FooElement(sess, id) for a typed live handle.

The JavaScript catalog with interactive demos lives at Alacris UI. What follows is the Go surface, grouped the way you reach for it.

WrapperTagTypical use
ui.Buttonui-buttonFilled, tonal, outlined, text. The default control.
ui.ButtonGroupui-button-groupRelated actions in a row.
ui.IconButtonui-icon-buttonIcon-only, for toolbars and cards.
ui.SplitButtonui-split-buttonA primary action plus a menu of alternatives.
ui.Fab / ui.FabMenuui-fab, ui-fab-menuFloating action, and a speed-dial of them.
ui.ToggleButton / ui.ToggleGroupui-toggle-button, ui-toggle-groupMutually exclusive or multi-select toggles.
WrapperTagTypical use
ui.TextFieldui-text-fieldLabels, supporting text, error, prefix/suffix.
ui.Searchui-searchFilter chrome. The example board uses this.
ui.Selectui-selectA closed list of options.
ui.Autocompleteui-autocompleteType-ahead over a list. Compose with chips for multi-select.
ui.Optionui-optionA choice inside select, autocomplete, or a menu.
ui.Checkboxui-checkboxA single boolean.
ui.Radio / ui.RadioGroupui-radio, ui-radio-groupExclusive choice.
ui.Switchui-switchOn/off with an immediate effect.
ui.Sliderui-sliderA numeric range.
ui.Ratingui-ratingStar (or similar) scoring.
ui.DatePicker / ui.TimePickerui-date-picker, ui-time-pickerCalendar and clock.
ui.Chip / ui.ChipSetui-chip, ui-chip-setFilter, input, and assist chips. Input chips dismiss.
WrapperTagTypical use
ui.AppBar / ui.BottomAppBarui-app-bar, ui-bottom-app-barTop or bottom bars.
ui.Toolbarui-toolbarA row of icon buttons and titles.
ui.NavRail / ui.NavItemui-nav-rail, ui-nav-itemSide navigation for larger screens.
ui.BottomNavui-bottom-navThree-to-five destinations on small screens.
ui.Drawerui-drawerA sliding pane of navigation or filters.
ui.Breadcrumbsui-breadcrumbsWhere this page sits.
ui.Tabs / ui.Tab / ui.TabPanelui-tabs, ui-tab, ui-tab-panelPeer views of the same object.
ui.Stepper / ui.Stepui-stepper, ui-stepA linear flow.
ui.Paginationui-paginationPage through a collection.
ui.Container / ui.Stack / ui.Surfaceui-container, ui-stack, ui-surfaceLayout and elevation, not chrome.
ui.Dividerui-dividerA rule between sections.
WrapperTagTypical use
ui.Dialogui-dialogModal editor. Nested dialogs need @change.stop / @click.stop on inner controls; delegated listeners skip nested shadow.
ui.Menu / ui.MenuItemui-menu, ui-menu-itemOverflow actions on a list or card.
ui.Sheet / ui.SideSheetui-sheet, ui-side-sheetBottom or side supplementary UI.
ui.Alertui-alertInline status.
ui.Snackbarui-snackbarA transient confirmation.
ui.Tooltipui-tooltipA label on hover or focus.
ui.Badgeui-badgeA count or mark on another control.
ui.Progress / ui.Spinner / ui.LoadingIndicatorui-progress, ui-spinner, ui-loading-indicatorDeterminate, indeterminate, and page-level wait.
ui.Skeletonui-skeletonA placeholder with the shape of the content.
ui.Backdropui-backdropDims the page behind a dialog or sheet.
WrapperTagTypical use
ui.Cardui-cardA contained unit: a lane, a summary, a media tile.
ui.List / ui.ListItemui-list, ui-list-itemRows with leading/trailing slots.
ui.Table / ui.TableToolbar / ui.TableFooterui-table, ui-table-toolbar, ui-table-footerTabular data with chrome.
ui.Textui-textTyped typography (headline, title, body, label).
ui.Avatarui-avatarA person or entity. Overlap them in a slot.
ui.Iconui-iconA named glyph from the theme’s icon set.
ui.Accordion / ui.AccordionItemui-accordion, ui-accordion-itemProgressive disclosure.
ui.Carousel / ui.CarouselItemui-carousel, ui-carousel-itemA horizontal pager.

ui.Tags is the source of truth for what Pending should hide. If a component is missing from the tables above, it is still in that slice.

Most “new components” you want are already in the catalog, composed. The example board:

  • Filter chrome is ui-search, not a custom input.
  • Labels are ui-chip inside ui-chip-set.
  • The card editor is ui-dialog with ui-text-field, ui-select, and chips. It stays in the tree (open=${() => !!draft()}) so opening it is a prop write, not a mount.
  • Member assignment is an app element (ala-member-select) that puts dismissible ui-chips next to ui-autocomplete. Multi-select the design system does not ship as one tag, built from two it does.
@ui.Button(ui.ButtonProps{Variant: "filled"}) { Save }
@app.Board(app.BoardProps{Items: items, Columns: columns}).ID("board")

Write a define() when the behaviour is yours: a live board whose cards must keep node identity under each(), a member picker that emits one change with a string array. Do not write a define() for a button.

The import map already has alacris for your module and @alacris/core for the design system, pointing at the same file.

A zero Theme is Material’s default seed (#e8ad18), Google Sans Flex, and light/dark from the OS. One colour re-skins everything:

A seed, a whole scheme
return alacris.Scripts(alacris.Config{
UI: true,
Theme: alacris.Theme{
Seed: "#0b57d0",
Scheme: "dark",
},
})
renders verified by go test
<script type="importmap">
{"imports":{"@alacris/core":"/_alacris/alacris.js","@alacris/ui":"/_alacris/ui/index.js","@alacris/ui/components/":"/_alacris/ui/components/","@alacris/ui/motion":"/_alacris/ui/motion/index.js","@alacris/ui/theme":"/_alacris/ui/theme/index.js","@alacris/ui/tokens":"/_alacris/ui/tokens/index.js","alacris":"/_alacris/alacris.js","alacris/context":"/_alacris/context.js","alacris/signal":"/_alacris/signal.js","alacris/store":"/_alacris/store.js"}}
</script>
<script type="module">
import { applyTheme, setScheme } from '@alacris/ui/theme';import '@alacris/ui';applyTheme({"seed":"#0b57d0"});setScheme("dark");
</script>

Config.UI loads the design system; Theme re-skins every component at once.

alacris.Config{
UI: true,
Theme: alacris.Theme{
Seed: "#0b57d0",
Scheme: "dark", // or "light", or "" / "auto" to follow the OS
},
}

Three places to intervene, from broadest to narrowest:

  1. Re-theme the system with Config.Theme. Every component follows. Dark mode is generated automatically.
  2. Re-skin one component type with ui-button { --ui-button-radius: 4px; } in your stylesheet, or ui.ButtonVars from Go for a single instance.
  3. Reach inside one instance with ui-dialog::part(surface) { … }.
FieldWhat it does
SeedOne colour the whole scheme is grown from.
ColorsNamed palettes when a seed is not enough: Primary, Secondary, Tertiary, Neutral, NeutralVariant, Error, Success, Warning, Info. Empty roles still derive from Seed.
Scheme"light", "dark", or "auto" (the default) to follow the OS.
Typography"google-sans-flex" (default), "google-sans", "roboto", "system", or a CSS family.
RadiusMultiplier on the Material shape scale. A pointer so 0 (square) is distinct from unset. 2 is extra round.
MotionMultiplier on durations. 0 is instant.
Density0, -1 or -2.
LoadFontsWhether applyTheme injects the typeface stylesheet. false when you self-host.
OverridesLast-write token maps: Common, Light, Dark. Keys are token names without the --ui- prefix (color-primary, radius-md).

Per-component contracts and ::part still work on top of a page theme. See Theming.

A generated wrapper is an *alacris.Element at render time. After the page is live, patch the same tag through a typed handle:

ui.ButtonElement(sess, "save").SetDisabled(true)

That is sess.Element("save") with compile-checked prop names. Use it when the element is an Alacris UI tag; use sess.Element("board") (or the generated BoardElement) for an app component.

ui.Button, ui.TextField, ui.Dialog, …Every tag in @alacris/ui.
ui.Tags / ui.Pending()For alacris.Pending.
ui.ButtonVars, …Per-component theming contracts.
ui.ButtonElement(sess, id)Typed live handles.
alacris.ThemeThe applyTheme config, on Config.

Run the example to see the catalog composed into a real page:

Terminal window
go run ./examples/todo