` tags in a template, or through the
`slots={...}` argument in Python.
#### CSortable slots
| Slot | Required | Data | Fallback |
|---|---|---|---|
| `default` | yes | `{}` ([`CSortableDefaultSlotData`](#sortable-interface-csortable-default-slot-data)) | None; accepts only Item declarations. |
#### CSortableItem slots
| Slot | Required | Data | Fallback |
|---|---|---|---|
| `default` | no | `{value, label, disabled, index}` ([`CSortableItemSlotData`](#sortable-interface-csortable-item-slot-data)) | Plain label text. |
| `handle` | no | `{value, label, disabled, index}` ([`CSortableItemSlotData`](#sortable-interface-csortable-item-slot-data)) | A neutral drag-grip glyph inside the owned Button. |
### Events
Component events are callback inputs supplied through `$c-props`. Native browser events
remain available through Alpine `@...` attributes.
#### CSortable events
| Event | Signature | Trigger and timing | Detail | Controlled and cancellation behavior |
|---|---|---|---|---|
| `onOrderChange` | `(order: string[], detail: CSortableOrderChangeDetail) => void` ([`CSortableOrderChangeDetail`](#sortable-interface-csortable-order-change-detail)) | A completed pointer keyboard reset or client reconciliation proposes another order. | `{order, previousOrder, value, fromIndex, toIndex, source, controlled, sourceEvent}` ([`CSortableOrderChangeDetail`](#sortable-interface-csortable-order-change-detail)) | Uncontrolled state commits first; controlled state requests and restores accepted order. |
### Methods
-
### CSS
CSS variables to theme the components. Set them on an ancestor or the component itself.
#### CSortable CSS variables
Apply these variables to `CSortable` or one of its ancestors.
| Variable | Type | Purpose | Default |
|---|---|---|---|
| `--cui-sortable-gap` | `length` | Space between Items. | `0.625rem` |
| `--cui-sortable-columns` | `grid-template-columns` | Responsive grid tracks. | `repeat(auto-fit, minmax(12rem, 1fr))` |
| `--cui-sortable-item-surface` | `color` | Item surface. | `Canvas` |
| `--cui-sortable-item-border` | `complete border` | Item and handle divider. | `Adaptive 1px neutral` |
| `--cui-sortable-item-radius` | `length` | Item and placeholder corners. | `0.625rem` |
| `--cui-sortable-item-shadow` | `box-shadow` | Moving Item elevation. | `Adaptive soft shadow` |
| `--cui-sortable-handle-size` | `length` | Minimum handle size. | `2.75rem` |
| `--cui-sortable-focus` | `color` | Handle focus and placeholder accent. | `Highlight` |
| `--cui-sortable-disabled-opacity` | `number` | Disabled Item opacity. | `0.55` |
### Attributes
HTML attributes defined on the components that you can refer to for CSS, inspection, and
testing. Read-only.
#### CSortable attributes
| Attribute | Element | Type | Meaning |
|---|---|---|---|
| `data-layout` | Root | `CSortableLayout` ([`CSortableLayout`](#sortable-interface-layout)) | Reflects current layout and collision profile. |
| `data-size` | Root | `CSortableSize` ([`CSortableSize`](#sortable-interface-size)) | Reflects density. |
| `data-disabled` | Root and disabled Items | `present | absent` | Reflects effective unavailability. |
| `data-dragging` | Root | `present | absent` | Marks any active pointer or keyboard move. |
| `data-moving` | Item | `present | absent` | Marks the actively moved Item. |
| `data-value` | Item | `string` | Exposes stable Item identity. |
### Selectors
Selectors for the DOM nodes in the components that you can use for CSS, inspection, and
testing.
#### CSortable selectors
| Selector | Element | Purpose |
|---|---|---|
| `[data-citry-ui-part="sortable"]` | Root div | Theme and reflected-state destination. |
| `[data-citry-ui-part="items"]` | Ordered list | Named collection, layout, and accepted DOM order. |
| `[data-citry-ui-part="item"]` | One Item | Stable Item customization. |
| `[data-citry-ui-part="handle"]` | Native Button | Pointer touch keyboard and focus owner. |
| `[data-citry-ui-part="content"]` | Item content div | Consumer presentation wrapper. |
| `[data-citry-ui-part="placeholder"]` | Temporary list item | Proposed pointer drop position. |
| `[data-citry-ui-part="status"]` | Polite live region | Reorder announcements. |
### Interfaces
Aliases and data shapes referenced above.
#### Input type aliases
| Interface | Definition |
|---|---|
| `CSortableLayout` | `Literal["vertical", "horizontal", "grid"]` |
| `CSortableSize` | `Literal["sm", "md", "lg"]` |
| `CSortableChangeSource` | `Literal["pointer", "keyboard", "reset", "client"]` |
| `CClassValue` | `str | Mapping[str, bool] | Sequence[CClassValue]` |
| `CStyleValue` | `str | Mapping[str, object] | Sequence[CStyleValue]` |
#### `CSortableDefaultSlotData`
Empty dataclass: `{}`.
#### `CSortableItemSlotData`
| Field | Type | Default | Meaning |
|---|---|---|---|
| `value` | `str` | - | Stable Item value. |
| `label` | `str` | - | Plain accessible label. |
| `disabled` | `bool` | - | Declared disabled state. |
| `index` | `int` | - | Initial zero-based accepted index. |
#### `CSortableOrderChangeDetail`
| Field | Type | Default | Meaning |
|---|---|---|---|
| `order` | `list[str]` | - | Requested or committed order. |
| `previousOrder` | `list[str]` | - | Accepted order before the move. |
| `value` | `str` | - | Moved Item value. |
| `fromIndex` | `int` | - | Previous zero-based index. |
| `toIndex` | `int` | - | Proposed zero-based index. |
| `source` | `CSortableChangeSource` ([`CSortableChangeSource`](#sortable-interface-change-source)) | - | Pointer keyboard reset or client cause. |
| `controlled` | `bool` | - | Whether client order owns accepted state. |
| `sourceEvent` | `object | None` | - | Native source Event or null. |
### Translation keys
Catalog keys used by this family. An explicit component input or slot listed in Override
takes precedence over the catalog for that instance.
#### CSortable translation keys
| Key | Purpose | Variables | Override | Browser updates |
|---|---|---|---|---|
| `citry-ui-sortable-label` | Names the collection. | `None.` | `label` | Stable `$c-tr` attribute. |
| `citry-ui-sortable-handle` | Names each handle. | `item: str` | `handle_label` with `{item}` | Stable reactive `$c-tr` attribute. |
| `citry-ui-sortable-instructions` | Explains keyboard operation. | `None.` | `instructions_label` | Server HTML; instructions do not change while a move is active. |
| `citry-ui-sortable-picked-up` | Announces pickup. | `item: str; position: str; total: str` | `picked_up_label` | One-shot `i18n.tr()` live-region output. |
| `citry-ui-sortable-moved` | Announces a proposed position. | `item: str; position: str; total: str` | `moved_label` | One-shot `i18n.tr()` live-region output. |
| `citry-ui-sortable-dropped` | Announces accepted drop. | `item: str; position: str; total: str` | `dropped_label` | One-shot `i18n.tr()` live-region output. |
| `citry-ui-sortable-cancelled` | Announces cancellation and restored position. | `item: str; position: str; total: str` | `cancelled_label` | One-shot `i18n.tr()` live-region output. |
---
# Table
Source: https://citry.dev/ui-library/components/table/
# Table
`CTable` renders finite, read-only tabular data with native HTML semantics. It
owns structure and presentation, not sorting, selection, editing, pagination,
or remote queries.
## Table at a glance
Line and outline variants, three densities, stripes, hover, column borders,
sticky headers, and explicit loading, empty, and error output share one native
Table model.
### Table at a glance
[Open the rendered preview](/ui-library/components/table/_previews/at-a-glance/)
````citry
from typing import Any
import citry_ui
from citry import Component, citry
from citry_ui import CTableColumn, CTableRow
citry.register_library(citry_ui)
class TableAtAGlance(Component):
class Kwargs:
pass
class Slots:
pass
template = """
Inner planets
Distance from the Sun
Outer planets
Distance from the Sun
Survey pending
No matching worlds
"""
css = """
:where(.table-glance) {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(min(100%, 20rem), 1fr));
gap: 1rem;
max-width: 72rem;
color: CanvasText;
font-family: ui-sans-serif, system-ui, sans-serif;
}
:where(.table-glance article) {
min-width: 0;
padding: 1rem;
border: 1px solid light-dark(#bfdbfe, #1e3a8a);
border-radius: 0.875rem;
background: Canvas;
}
:where(.table-glance h2) {
margin: 0 0 0.75rem;
color: light-dark(#1d4ed8, #93c5fd);
font-size: 1rem;
}
"""
def template_data(self, kwargs: Kwargs, slots: Slots) -> dict[str, Any]: # noqa: ARG002
return {
"columns": (
CTableColumn("planet", "Planet", row_header=True),
CTableColumn("distance", "Mean distance", align="end"),
),
"inner_rows": (
CTableRow("mercury", {"planet": "Mercury", "distance": "57.9 million km"}),
CTableRow("venus", {"planet": "Venus", "distance": "108.2 million km"}),
CTableRow("earth", {"planet": "Earth", "distance": "149.6 million km"}),
),
"outer_rows": (
CTableRow("jupiter", {"planet": "Jupiter", "distance": "778.5 million km"}),
CTableRow("saturn", {"planet": "Saturn", "distance": "1.43 billion km"}),
CTableRow("uranus", {"planet": "Uranus", "distance": "2.87 billion km"}),
),
}
preview = TableAtAGlance()
preview # noqa: B018
````
`CTable` has no component JavaScript or client inputs. Every Table input is a
server input passed through ` ` or `CTable(...)`. Controls
inside cells keep their own client props and native events.
## Build a Table
Declare columns once, then give every keyed row exactly one value per column.
### List the moons of Jupiter
[Open the rendered preview](/ui-library/components/table/_previews/moons-of-jupiter/)
````citry
from typing import Any
import citry_ui
from citry import Component, citry
from citry_ui import CTableColumn, CTableRow
citry.register_library(citry_ui)
class MoonsOfJupiter(Component):
class Kwargs:
pass
class Slots:
pass
template = """
"""
css = """
:where(.moon-table) {
max-width: 48rem;
color: CanvasText;
font-family: ui-sans-serif, system-ui, sans-serif;
}
:where(.moon-table [data-column-key="diameter"]) {
font-variant-numeric: tabular-nums;
}
"""
def template_data(self, kwargs: Kwargs, slots: Slots) -> dict[str, Any]: # noqa: ARG002
return {
"columns": (
CTableColumn("moon", "Moon", row_header=True),
CTableColumn("discoverer", "Discoverer"),
CTableColumn("diameter", "Diameter", align="end"),
),
"rows": (
CTableRow("io", {"moon": "Io", "discoverer": "Galileo", "diameter": "3,643 km"}),
CTableRow("europa", {"moon": "Europa", "discoverer": "Galileo", "diameter": "3,122 km"}),
CTableRow("ganymede", {"moon": "Ganymede", "discoverer": "Galileo", "diameter": "5,268 km"}),
CTableRow("callisto", {"moon": "Callisto", "discoverer": "Galileo", "diameter": "4,821 km"}),
),
}
preview = MoonsOfJupiter()
preview # noqa: B018
````
```citry-html
Galilean moons
```
```python
from citry_ui import CTable, CTableColumn, CTableRow
moon_table = CTable(
columns=(
CTableColumn("moon", "Moon", row_header=True),
CTableColumn("diameter", "Diameter", align="end"),
),
rows=(
CTableRow("europa", {"moon": "Europa", "diameter": "3,122 km"}),
),
slots={"caption": "Galilean moons"},
)
```
Keys are stable application identity, not display text or array positions.
They must be unique and non-empty. Row and column keys are exposed in escaped
`data-*` attributes, so do not put secrets in them.
Use one `row_header=True` column for the entity or category that identifies
each row. `align="end"` follows text direction and suits numeric values. Add
tabular numerals through `cell_attrs`, a class, or the public cell selector.
## Present rich cells
Raw values are escaped. A `CTableCell` adds attributes to one position, and a
component-like value renders directly. Use the generic `cell` fill when output
depends on the current row and column.
### Build an observation catalog
[Open the rendered preview](/ui-library/components/table/_previews/rich-cells/)
````citry
from typing import Any
import citry_ui
from citry import Component, citry
from citry_ui import CTableCell, CTableColumn, CTableRow
citry.register_library(citry_ui)
class ObservationCatalog(Component):
class Kwargs:
pass
class Slots:
pass
template = """
Tonight's observation catalog
{{ cell.value }}
View {{ row.key }}
{{ cell.value }}
"""
css = """
:where(.observation-catalog) {
max-width: 58rem;
color: CanvasText;
font-family: ui-sans-serif, system-ui, sans-serif;
}
:where(.visibility) {
display: inline-flex;
padding: 0.2rem 0.55rem;
border-radius: 999px;
font-size: 0.75rem;
font-weight: 700;
text-transform: capitalize;
}
:where(.visibility--excellent) {
color: light-dark(#166534, #bbf7d0);
background: light-dark(#dcfce7, #14532d);
}
:where(.visibility--limited) {
color: light-dark(#9a3412, #fed7aa);
background: light-dark(#ffedd5, #7c2d12);
}
"""
def template_data(self, kwargs: Kwargs, slots: Slots) -> dict[str, Any]: # noqa: ARG002
return {
"columns": (
CTableColumn("target", "Target", row_header=True),
CTableColumn("type", "Type"),
CTableColumn("visibility", "Visibility"),
CTableColumn("action", "Actions"),
),
"rows": (
CTableRow(
"orion-nebula",
{"target": "Orion Nebula", "type": "Nebula", "visibility": "excellent", "action": None},
),
CTableRow(
"andromeda",
{
"target": CTableCell("Andromeda Galaxy", attrs={"class": "featured-target"}),
"type": "Galaxy",
"visibility": "limited",
"action": None,
},
),
),
}
preview = ObservationCatalog()
preview # noqa: B018
````
```citry-html
View {{ row.key }}
{{ cell.value }}
```
`header_attrs` targets one column header. `cell_attrs` supplies defaults to
every body cell in that column. `CTableCell.attrs` wins for ordinary duplicate
attributes while class and style contributions merge. Structural values such
as scopes and spans remain Table-owned.
Sorting links, row actions, checkboxes, Inputs, and Comboboxes may live in
cells, but their behavior belongs to those controls. Hover never makes a row
selectable or clickable.
## Add totals and summaries
Set one or more column `footer` values to render a native one-row `tfoot`.
Footer content may be plain text or another component. `footer_attrs` targets
that column's footer cell.
### Summarize telescope time
[Open the rendered preview](/ui-library/components/table/_previews/survey-totals/)
````citry
from typing import Any
import citry_ui
from citry import Component, citry
from citry_ui import CTableColumn, CTableRow
citry.register_library(citry_ui)
class SurveyTotals(Component):
class Kwargs:
pass
class Slots:
pass
template = """
Telescope survey time
{{ value }}
{{ value }}
"""
css = """
:where(.survey-totals) {
max-width: 44rem;
color: CanvasText;
font-family: ui-sans-serif, system-ui, sans-serif;
}
:where(.survey-totals [data-citry-ui-part="footer-cell"]) {
color: light-dark(#1e3a8a, #bfdbfe);
}
"""
def template_data(self, kwargs: Kwargs, slots: Slots) -> dict[str, Any]: # noqa: ARG002
return {
"columns": (
CTableColumn("program", "Program", row_header=True, footer="Total"),
CTableColumn("instrument", "Instrument", footer="3 programs"),
CTableColumn(
"hours",
"Hours",
align="end",
cell_attrs={"style": {"font-variant-numeric": "tabular-nums"}},
footer="84.5",
),
),
"rows": (
CTableRow("aurora", {"program": "Aurora survey", "instrument": "Spectrograph", "hours": "36.0"}),
CTableRow("rings", {"program": "Ring survey", "instrument": "Wide-field camera", "hours": "28.5"}),
CTableRow("comets", {"program": "Comet survey", "instrument": "Infrared camera", "hours": "20.0"}),
),
}
preview = SurveyTotals()
preview # noqa: B018
````
The `footer` fill receives `{column, value, column_index}` once per footer
cell. Its fallback is the matching column value. The row-header column remains
a row header in the footer.
Version 1 owns one summary row. Multiple footer rows, grouped headers,
`rowspan`, `colspan`, and `colgroup` need a future logical-grid schema.
## Choose appearance
### Compare Table appearance
[Open the rendered preview](/ui-library/components/table/_previews/appearance/)
````citry
from typing import Any
import citry_ui
from citry import Component, citry
from citry_ui import CTableColumn, CTableRow
citry.register_library(citry_ui)
class TableAppearance(Component):
class Kwargs:
pass
class Slots:
pass
template = """
Line · comfortable
Outline · compact
Striped · default
Hover · bottom caption
Hover highlights, but never selects, a row.
"""
css = """
:where(.table-appearance) {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(min(100%, 22rem), 1fr));
gap: 1rem;
max-width: 72rem;
color: CanvasText;
font-family: ui-sans-serif, system-ui, sans-serif;
}
:where(.table-appearance article) {
min-width: 0;
padding: 1rem;
border: 1px solid light-dark(#dbeafe, #1e3a8a);
border-radius: 0.875rem;
background: Canvas;
}
:where(.table-appearance h2) {
margin: 0 0 0.75rem;
font-size: 0.875rem;
}
"""
def template_data(self, kwargs: Kwargs, slots: Slots) -> dict[str, Any]: # noqa: ARG002
return {
"columns": (
CTableColumn("planet", "Planet", row_header=True),
CTableColumn("gravity", "Gravity", align="end"),
),
"rows": (
CTableRow("mars", {"planet": "Mars", "gravity": "3.71 m/s²"}),
CTableRow("neptune", {"planet": "Neptune", "gravity": "11.15 m/s²"}),
),
}
preview = TableAppearance()
preview # noqa: B018
````
- `variant="line"` separates rows; `outline` also frames the root.
- `density` accepts `default`, `comfortable`, or `compact`.
- `striped` alternates ready-row surfaces.
- `hover` adds pointer feedback without behavior.
- `column_borders` adds vertical separators.
- `caption_side` places a native caption at the top or bottom.
- `layout="fixed"` uses native fixed table layout; set widths through column
attribute styles, classes, or public selectors.
These are server inputs. Side-by-side examples show their output without
pretending that Table owns browser-reactive configuration.
## Show loading, empty, and error output
### Show survey states
[Open the rendered preview](/ui-library/components/table/_previews/states/)
````citry
from typing import Any
import citry_ui
from citry import Component, citry
from citry_ui import CTableColumn
citry.register_library(citry_ui)
class TableStates(Component):
class Kwargs:
pass
class Slots:
pass
template = """
Loading
Receiving deep-space survey...
Empty
Error
"""
css = """
:where(.table-states) {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(min(100%, 19rem), 1fr));
gap: 1rem;
max-width: 68rem;
color: CanvasText;
font-family: ui-sans-serif, system-ui, sans-serif;
}
:where(.table-states article) {
min-width: 0;
padding: 1rem;
border: 1px solid light-dark(#c7d2fe, #3730a3);
border-radius: 0.875rem;
background: Canvas;
}
:where(.table-states h2) {
margin: 0 0 0.75rem;
font-size: 0.875rem;
}
"""
def template_data(self, kwargs: Kwargs, slots: Slots) -> dict[str, Any]: # noqa: ARG002
return {
"columns": (
CTableColumn("signal", "Signal", row_header=True),
CTableColumn("strength", "Strength", align="end"),
),
}
preview = TableStates()
preview # noqa: B018
````
The header stays visible. Loading, empty, and error replace body rows with one
native cell spanning every column. Loading sets `aria-busy` on the Table.
Configured footers appear only in ready output, including ready-empty output.
The `loading`, `empty`, and `error` slots change visible content. Their matching
label inputs also feed a persistent polite live region outside the busy Table.
Keep each label consistent with its custom slot.
Entering a state removes stale ready rows. Returning to ready renders the next
complete keyed collection.
## Keep wide and long Tables usable
`overflow="auto"` is the default. It preserves native row and column
relationships and lets two-dimensional data scroll horizontally at narrow
widths or high zoom.
### Keep headers visible
[Open the rendered preview](/ui-library/components/table/_previews/sticky-overflow/)
````citry
from typing import Any
import citry_ui
from citry import Component, citry
from citry_ui import CTableColumn, CTableRow
citry.register_library(citry_ui)
class StickyOverflowTable(Component):
class Kwargs:
pass
class Slots:
pass
template = """
Bounded catalog
Scroll this region in either direction.
Confirmed exoplanets
Page-sticky mode
The header follows page scroll instead of an inner scroller.
Nearby exoplanets
"""
css = """
:where(.sticky-tables) {
display: grid;
gap: 1.25rem;
max-width: 64rem;
color: CanvasText;
font-family: ui-sans-serif, system-ui, sans-serif;
}
:where(.sticky-tables article) {
min-width: 0;
padding: 1rem;
border: 1px solid light-dark(#bae6fd, #075985);
border-radius: 0.875rem;
background: Canvas;
}
:where(.sticky-tables h2, .sticky-tables p) {
margin: 0;
}
:where(.sticky-tables p) {
margin-block: 0.25rem 0.75rem;
color: color-mix(in srgb, currentColor 68%, transparent);
}
"""
def template_data(self, kwargs: Kwargs, slots: Slots) -> dict[str, Any]: # noqa: ARG002
columns = (
CTableColumn("planet", "Planet", row_header=True, cell_attrs={"style": {"width": "12rem"}}),
CTableColumn("system", "System", cell_attrs={"style": {"width": "14rem"}}),
CTableColumn("distance", "Distance", align="end", cell_attrs={"style": {"width": "10rem"}}),
CTableColumn("period", "Orbital period", align="end", cell_attrs={"style": {"width": "10rem"}}),
)
rows = tuple(
CTableRow(
key,
{"planet": planet, "system": system, "distance": distance, "period": period},
)
for key, planet, system, distance, period in (
("proxima-b", "Proxima Centauri b", "Proxima Centauri", "4.2 ly", "11.2 days"),
("barnard-b", "Barnard's Star b", "Barnard's Star", "6.0 ly", "233 days"),
("ross-128-b", "Ross 128 b", "Ross 128", "11.0 ly", "9.9 days"),
("tau-ceti-e", "Tau Ceti e", "Tau Ceti", "11.9 ly", "163 days"),
("gj-1061-d", "GJ 1061 d", "GJ 1061", "12.0 ly", "13.0 days"),
("teegarden-b", "Teegarden's Star b", "Teegarden's Star", "12.5 ly", "4.9 days"),
("wolf-1061-c", "Wolf 1061 c", "Wolf 1061", "14.1 ly", "17.9 days"),
("gliese-667-cc", "Gliese 667 Cc", "Gliese 667 C", "23.6 ly", "28.1 days"),
)
)
return {"columns": columns, "rows": rows}
preview = StickyOverflowTable()
preview # noqa: B018
````
For a bounded scroller, combine `sticky_header=True` with a block-size limit:
```citry-html
```
For a header that follows page scroll, use `sticky_header=True` with
`overflow="visible"`. The two modes have different scroll ancestors.
Auto overflow always adds one keyboard focus stop because a zero-JavaScript
component cannot measure overflow before deciding. A caption names that region.
Without a caption, set `scroll_label` or name the native Table with
`table_attrs={"aria-label": ...}`. The focus ring stays visible.
An auto-overflow wrapper can clip inline menus, listboxes, and other overlays.
Use a top-layer or portaled overlay when available, or choose visible overflow
when the page can contain the Table.
## Preserve native semantics and focus
Column headers use ``. The optional row-header column uses
` `; other cells use ` `. A caption supplies the Table's
native accessible name. Use `table_attrs` for `aria-label`, `aria-labelledby`,
or `aria-describedby` when visible caption text is not appropriate.
Table does not use `role="grid"`, move focus with arrow keys, or select rows.
Tab order contains the auto-overflow wrapper and focusable content supplied in
cells. Native table navigation remains available to assistive technology.
Ready rows use private Citry morph keys. Reordering preserves a surviving row
subtree and its control state where Citry can preserve the control. Removing a
row removes its complete subtree. Table does not guess a new focus target.
Sorting, filtering, pagination, and selection belong to controls composed
around the Table. Those controls update server state and render the next
complete `columns` and `rows`; they are not Table callbacks.
## Theme and customize Table
Use `class_`, `style`, public CSS variables, or documented selectors. Do not
target private `.cui-*` classes or `--_cui-*` variables.
### Theme observatory Tables
[Open the rendered preview](/ui-library/components/table/_previews/theme-customization/)
````citry
from typing import Any
import citry_ui
from citry import Component, citry
from citry_ui import CTableColumn, CTableRow
citry.register_library(citry_ui)
class ObservatoryTables(Component):
class Kwargs:
pass
class Slots:
pass
template = """
Night observation
Winter sky
Solar observation
Daylight calibration
"""
css = """
:where(.observatory-tables) {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(min(100%, 22rem), 1fr));
gap: 1rem;
max-width: 70rem;
font-family: ui-sans-serif, system-ui, sans-serif;
}
:where(.observatory-tables article) {
min-width: 0;
padding: 1rem;
border-radius: 0.875rem;
}
:where(.observatory-tables h2) {
margin: 0 0 0.75rem;
font-size: 1rem;
}
:where(.observatory-tables__night) {
color-scheme: dark;
color: #e0f2fe;
background: #0c1b33;
--cui-table-background: #102a43;
--cui-table-foreground: #e0f2fe;
--cui-table-border-color: #486581;
--cui-table-header-background: #243b53;
--cui-table-striped-background: #173a5e;
}
:where(.observatory-tables__day) {
color-scheme: light;
color: #422006;
background: #fffbeb;
--cui-table-border-color: #f59e0b;
}
:where(.observatory-tables [data-citry-ui-part="footer-cell"]) {
letter-spacing: 0.02em;
}
"""
def template_data(self, kwargs: Kwargs, slots: Slots) -> dict[str, Any]: # noqa: ARG002
return {
"columns": (
CTableColumn("star", "Star", row_header=True, footer="Brightest"),
CTableColumn("magnitude", "Magnitude", align="end", footer="-1.46"),
),
"rows": (
CTableRow("sirius", {"star": "Sirius", "magnitude": "-1.46"}),
CTableRow("canopus", {"star": "Canopus", "magnitude": "-0.74"}),
CTableRow("arcturus", {"star": "Arcturus", "magnitude": "-0.05"}),
),
}
preview = ObservatoryTables()
preview # noqa: B018
````
Variables inherit, so one ancestor can theme several Tables. Set a variable on
one root for an isolated override. Public selectors such as
`[data-citry-ui-part="footer-cell"]` target stable elements. Reflected
attributes expose the selected visual configuration for CSS and inspection.
Nested Tables resolve their own density and variant rules. Structural styles
from an outer Table do not stripe, hover, border, or resize an inner Table.
Public color variables may intentionally inherit unless the nested root
overrides them.
## Support direction, long content, and print
### Read translated star names
[Open the rendered preview](/ui-library/components/table/_previews/environment/)
````citry
from typing import Any
import citry_ui
from citry import Component, citry
from citry_ui import CTable, CTableColumn, CTableRow
citry.register_library(citry_ui)
class TableEnvironment(Component):
class Kwargs:
pass
class Slots:
pass
template = """
أسماء النجوم
أسماء عربية وتقليدية للنجوم
"""
css = """
:where(.table-environment) {
max-width: 34rem;
color: CanvasText;
font-family: ui-sans-serif, system-ui, sans-serif;
}
:where(.table-environment h2) {
margin: 0 0 0.75rem;
color: light-dark(#6d28d9, #c4b5fd);
font-size: 1rem;
}
:where(.table-environment [data-column-key="notes"]) {
min-width: 18rem;
white-space: normal;
overflow-wrap: anywhere;
}
"""
def template_data(self, kwargs: Kwargs, slots: Slots) -> dict[str, Any]: # noqa: ARG002
nested = CTable(
columns=(CTableColumn("planet", "الكوكب"),),
rows=(CTableRow("earth", {"planet": "الأرض"}),),
density="compact",
overflow="visible",
slots={"caption": "نظام نجمي"},
)
return {
"columns": (
CTableColumn("name", "الاسم", row_header=True),
CTableColumn("meaning", "المعنى"),
CTableColumn("notes", "ملاحظات"),
),
"rows": (
CTableRow(
"betelgeuse",
{
"name": "منكب الجوزاء",
"meaning": "كتف الجبار",
"notes": "نجم أحمر فائق الضخامة في كوكبة الجبار، واسمه التقليدي طويل عند نقله بين اللغات.",
},
),
CTableRow(
"nested",
{"name": "الشمس", "meaning": "نجمنا", "notes": nested},
),
),
}
preview = TableEnvironment()
preview # noqa: B018
````
Logical alignment follows LTR and RTL. Long text wraps by default; use fixed
layout and explicit widths only when truncation or stable columns improve the
task. At narrow widths and 400% zoom, surrounding content still reflows while
the Table may scroll as a two-dimensional exception.
Default colors support light and dark scopes. Forced colors retains text,
focus, and borders without using stripes or hover as the only signal. Print
removes overflow clipping and sticky positioning.
`CTable` targets ordinary finite collections. The repository's diagnostic
scaling harness records server rendering at 10, 100, and 1,000 rows; hosted
results remain release evidence, not a performance guarantee. Virtualization,
grouped headers, interactive grid navigation, editing, and remote collection
ownership belong to a future DataTable/DataGrid.
## API reference
### Inputs
#### CTable server inputs
Server inputs are passed in a template through ` ` or in Python through
`CTable(...)`.
| Input | Type | Default | Effect |
|---|---|---|---|
| `columns` | `Sequence[CTableColumn]` | required | Defines the structural column schema. |
| `rows` | `Sequence[CTableRow]` | required | Defines the keyed server-owned collection. |
| `state` | `"ready" | "loading" | "error"` ([`CTableState`](#table-interface-input-type-aliases-ctable-state)) | `"ready"` | Selects body output. Ready with no rows selects empty output. |
| `id` | `str | None` | generated | Sets wrapper and caption identity. |
| `variant` | `"line" | "outline"` ([`CTableVariant`](#table-interface-input-type-aliases-ctable-variant)) | `"line"` | Selects border presentation. |
| `density` | `"default" | "comfortable" | "compact"` ([`CTableDensity`](#table-interface-input-type-aliases-ctable-density)) | `"comfortable"` | Selects cell sizing. |
| `striped` | `bool` | `False` | Adds alternating ready-row backgrounds. |
| `hover` | `bool` | `False` | Adds pointer hover feedback without adding row behavior. |
| `sticky_header` | `bool` | `False` | Sticks header cells within the scroll ancestor. |
| `column_borders` | `bool` | `False` | Adds vertical separators. |
| `layout` | `"auto" | "fixed"` ([`CTableLayout`](#table-interface-input-type-aliases-ctable-layout)) | `"auto"` | Selects native `table-layout`. |
| `overflow` | `"auto" | "visible"` ([`CTableOverflow`](#table-interface-input-type-aliases-ctable-overflow)) | `"auto"` | Selects horizontal wrapper behavior. |
| `caption_side` | `"top" | "bottom"` ([`CTableCaptionSide`](#table-interface-input-type-aliases-ctable-caption-side)) | `"top"` | Places the native caption. |
| `scroll_label` | `non-empty str | None` | Uses the caption or native Table ARIA name when available. | Names the `overflow="auto"` focusable region. |
| `loading_label` | `non-empty str` | `"Loading data..."` | Sets the loading fallback and persistent polite announcement text. |
| `empty_label` | `non-empty str` | `"No data."` | Sets the empty fallback and persistent polite announcement text. |
| `error_label` | `non-empty str` | `"Unable to load data."` | Sets the error fallback and persistent polite announcement text. |
| `class_` | `str | Mapping[str, bool] | Sequence[CClassValue] | None` ([`CClassValue`](#table-interface-input-type-aliases-class-value)) | `None` | Adds wrapper classes from a string, conditional mapping, or nested sequence and merges them with `attrs`. |
| `style` | `str | Mapping[str, str | int | float | bool | None] | Sequence[CStyleValue] | None` ([`CStyleValue`](#table-interface-input-type-aliases-style-value)) | `None` | Adds wrapper inline styles from CSS text, a property mapping, or a nested sequence and merges them with `attrs`. |
| `attrs` | `Mapping[str, object] | None` | `None` | Adds allowed wrapper attributes; prefer the top-level inputs for class and style. |
| `table_attrs` | `Mapping[str, object] | None` | `None` | Adds allowed native table and ARIA attributes. |
### Slots
Slots are passed as nested content or `` tags in a template, or through the
`slots={...}` argument in Python.
#### CTable slots
| Slot | Required | Data | Fallback |
|---|---|---|---|
| `caption` | no | `{}` ([`CTableCaptionSlotData`](#table-interface-ctable-caption-slot-data)) | No caption. |
| `header` | no | `{column: CTableColumn, column_index: int}` ([`CTableHeaderSlotData`](#table-interface-ctable-header-slot-data)) | Escaped column label. |
| `cell` | no | `{row: CTableRow, column: CTableColumn, cell: CTableCell, row_index: int, column_index: int}` ([`CTableCellSlotData`](#table-interface-ctable-cell-slot-data)) | Escaped or component-like cell value. |
| `footer` | no | `{column: CTableColumn, value: object | None, column_index: int}` ([`CTableFooterSlotData`](#table-interface-ctable-footer-slot-data)) | Escaped or component-like column footer value. |
| `empty` | no | `{}` ([`CTableEmptySlotData`](#table-interface-ctable-empty-slot-data)) | `empty_label` |
| `loading` | no | `{}` ([`CTableLoadingSlotData`](#table-interface-ctable-loading-slot-data)) | `loading_label` |
| `error` | no | `{}` ([`CTableErrorSlotData`](#table-interface-ctable-error-slot-data)) | `error_label` |
### Events
-
### Methods
-
### CSS
CSS variables to theme the components. Set them on an ancestor or the component itself.
#### CTable CSS variables
Apply these variables to `CTable` or one of its ancestors.
| Variable | Type | Purpose | Default |
|---|---|---|---|
| `--cui-table-background` | `color` | Table surface. | `Canvas` |
| `--cui-table-foreground` | `color` | Primary text. | `CanvasText` |
| `--cui-table-muted-foreground` | `color` | Caption and subdued status text. | `Muted CanvasText mix.` |
| `--cui-table-border-color` | `color` | Row, outline, footer, and optional column borders. | `Subtle CanvasText mix.` |
| `--cui-table-header-background` | `color` | Header surface, including sticky headers. | `Subtle CanvasText/Canvas mix.` |
| `--cui-table-footer-background` | `color` | Footer surface. | `Subtle CanvasText/Canvas mix.` |
| `--cui-table-striped-background` | `color` | Alternating ready-row surface. | `Subtle CanvasText/Canvas mix.` |
| `--cui-table-hover-background` | `color` | Ready-row pointer hover surface. | `Subtle Highlight/Canvas mix.` |
| `--cui-table-error-foreground` | `color` | Error status text. | `Scheme-aware negative color.` |
| `--cui-table-focus-color` | `color` | Overflow-region focus ring. | `Highlight` |
| `--cui-table-radius` | `length` | Outline and wrapper radius. | `0.625rem` |
| `--cui-table-cell-block-padding` | `length` | Logical block cell padding. | `Density-derived length.` |
| `--cui-table-cell-inline-padding` | `length` | Logical inline cell padding. | `Density-derived length.` |
| `--cui-table-caption-padding` | `CSS padding shorthand` | Caption spacing. | `0.75rem 1rem` |
| `--cui-table-min-width` | `length` | Minimum width before horizontal overflow. | `32rem` |
| `--cui-table-sticky-offset` | `length` | Sticky header block offset. | `0px` |
### Attributes
HTML attributes defined on the components that you can refer to for CSS, inspection, and
testing. Read-only.
#### CTable attributes
| Attribute | Element | Type | Meaning |
|---|---|---|---|
| `data-state` | Root | `"ready" | "loading" | "error"` | Mirrors effective body-output state. |
| `data-variant` | Root | `"line" | "outline"` | Mirrors effective border presentation. |
| `data-density` | Root | `"default" | "comfortable" | "compact"` | Mirrors effective cell density. |
| `data-striped` | Root | `present | absent` | Mirrors striped-row presentation. |
| `data-hover` | Root | `present | absent` | Mirrors pointer-hover presentation. |
| `data-sticky-header` | Root | `present | absent` | Mirrors sticky-header configuration. |
| `data-column-borders` | Root | `present | absent` | Mirrors column-border presentation. |
| `data-layout` | Root | `"auto" | "fixed"` | Mirrors effective native table layout. |
| `data-overflow` | Root | `"auto" | "visible"` | Mirrors horizontal overflow behavior. |
| `data-caption-side` | Root | `"top" | "bottom"` | Mirrors effective caption placement. |
#### CTable attributes
| Attribute | Element | Type | Meaning |
|---|---|---|---|
| `data-row-key` | Ready row | `string` | Canonical row identity. |
#### CTable attributes
| Attribute | Element | Type | Meaning |
|---|---|---|---|
| `data-column-key` | Header, body, or footer cell | `string` | Canonical column identity. |
| `data-align` | Header, body, or footer cell | `"start" | "center" | "end"` | Logical cell alignment. |
### Selectors
Selectors for the DOM nodes in the components that you can use for CSS, inspection, and
testing.
#### CTable selectors
| Selector | Element | Purpose |
|---|---|---|
| `[data-citry-ui-part="root"]` | Root | Wrapper, scroll container, and `attrs` destination. |
| `[data-citry-ui-part="table"]` | Native Table | Table and `table_attrs` destination. |
| `[data-citry-ui-part="caption"]` | Native caption | Optional caption hook. |
| `[data-citry-ui-part="header"]` | Header group | Native header group. |
| `[data-citry-ui-part="header-row"]` | Header row | Native header row. |
| `[data-citry-ui-part="header-cell"]` | Header cell | Column-header hook. |
| `[data-citry-ui-part="body"]` | Body group | Native body group. |
| `[data-citry-ui-part="row"]` | Ready row | Keyed row hook. |
| `[data-citry-ui-part="cell"]` | Body cell | Ready data-cell or row-header hook. |
| `[data-citry-ui-part="state-row"]` | State row | Loading, empty, or error row. |
| `[data-citry-ui-part="state-cell"]` | State cell | Cell spanning every column. |
| `[data-citry-ui-part="loading"]` | Loading region | Loading status content. |
| `[data-citry-ui-part="empty"]` | Empty region | Empty status content. |
| `[data-citry-ui-part="error"]` | Error region | Error status content. |
| `[data-citry-ui-part="footer"]` | Native footer group | Optional summary group. |
| `[data-citry-ui-part="footer-row"]` | Native footer row | One summary row. |
| `[data-citry-ui-part="footer-cell"]` | Footer cell | Per-column summary cell. |
### Interfaces
Aliases and data shapes referenced above.
#### Input type aliases
| Interface | Definition |
|---|---|
| `CClassValue` | `str | Mapping[str, bool] | Sequence[CClassValue]` |
| `CStyleValue` | `str | Mapping[str, str | int | float | bool | None] | Sequence[CStyleValue]` |
| `CTableState` | `Literal["ready", "loading", "error"]` |
| `CTableVariant` | `Literal["line", "outline"]` |
| `CTableDensity` | `Literal["default", "comfortable", "compact"]` |
| `CTableAlign` | `Literal["start", "center", "end"]` |
| `CTableLayout` | `Literal["auto", "fixed"]` |
| `CTableOverflow` | `Literal["auto", "visible"]` |
| `CTableCaptionSide` | `Literal["top", "bottom"]` |
#### `CTableColumn`
| Field | Type | Default | Meaning |
|---|---|---|---|
|
`key` | `non-empty str` | required | Unique column identity. |
|
`label` | `non-empty str` | required | Default escaped header content. |
|
`row_header` | `bool` | False | Renders body cells in this column as `
`. |
| `align` | `"start" | "center" | "end"` ([`CTableAlign`](#table-interface-input-type-aliases-ctable-align)) | "start" | Sets logical header and cell alignment. |
| `header_attrs` | `Mapping[str, object] | None` | None | Adds allowed native attributes to the column header. |
| `cell_attrs` | `Mapping[str, object] | None` | None | Adds defaults to every body cell in the column; a specific `CTableCell.attrs` value wins while class and style merge. |
| `footer` | `object | None` | None | Supplies fallback content for the optional footer cell. Any non-None value enables the footer. |
| `footer_attrs` | `Mapping[str, object] | None` | None | Adds allowed native attributes to the footer cell. |
#### `CTableRow`
| Field | Type | Default | Meaning |
|---|---|---|---|
| `key` | `non-empty str` | required | Unique row and morph identity. |
| `cells` | `Mapping[str, object | CTableCell]` | required | Supplies exactly one value for every declared column key. |
| `attrs` | `Mapping[str, object] | None` | None | Adds allowed native attributes to the row. |
#### `CTableCell`
| Field | Type | Default | Meaning |
|---|---|---|---|
| `value` | `object` | required | Default escaped or component-like cell content. |
| `attrs` | `Mapping[str, object] | None` | None | Adds allowed native cell attributes. |
#### `CTableCaptionSlotData`
Empty dataclass: `{}`.
#### `CTableHeaderSlotData`
| Field | Type | Default | Meaning |
|---|---|---|---|
| `column` | `CTableColumn` | - | Current column declaration. |
| `column_index` | `int` | - | Zero-based column position. |
#### `CTableCellSlotData`
| Field | Type | Default | Meaning |
|---|---|---|---|
| `row` | `CTableRow` | - | Current row declaration. |
| `column` | `CTableColumn` | - | Current column declaration. |
| `cell` | `CTableCell` | - | Normalized cell declaration. |
| `row_index` | `int` | - | Zero-based row position. |
| `column_index` | `int` | - | Zero-based column position. |
#### `CTableFooterSlotData`
| Field | Type | Default | Meaning |
|---|---|---|---|
| `column` | `CTableColumn` | - | Current column declaration. |
| `value` | `object | None` | - | Current column footer value. |
| `column_index` | `int` | - | Zero-based column position. |
#### `CTableEmptySlotData`
Empty dataclass: `{}`.
#### `CTableLoadingSlotData`
Empty dataclass: `{}`.
#### `CTableErrorSlotData`
Empty dataclass: `{}`.
### Translation keys
Catalog keys used by this family. An explicit component input or slot listed in Override
takes precedence over the catalog for that instance.
#### CTable translation keys
| Key | Purpose | Variables | Override | Browser updates |
|---|---|---|---|---|
| `citry-ui-table-loading` | Labels and announces the loading state. | `None` | `loading_label` input or `loading` slot | $c-tr updates component fallback text and the announcer. |
| `citry-ui-table-empty` | Labels and announces an empty ready state. | `None` | `empty_label` input or `empty` slot | $c-tr updates component fallback text and the announcer. |
| `citry-ui-table-error` | Labels and announces the error state. | `None` | `error_label` input or `error` slot | $c-tr updates component fallback text and the announcer. |
---
# Tag and TagGroup
Source: https://citry.dev/ui-library/components/tag/
# Tag and TagGroup
Use `CTagGroup` for a labelled collection of compact categories, filters, or
keywords. A descriptive group renders list semantics. Selection, actions, or
removal switch it to one keyboard-operable grid.
### TagGroup at a glance
[Open the rendered preview](/ui-library/components/tag/_previews/at-a-glance/)
````citry
import citry_ui
from citry import Component, citry
citry.register_library(citry_ui)
class TagGlance(Component):
template = """
CSS
HTML
Accessibility
Wi-Fi
Parking
Pool
"""
preview = TagGlance()
preview # noqa: B018
````
```citry-html
CSS
HTML
```
## Select Tags
Choose a selection mode and give every Tag a unique value.
### Select Tags
[Open the rendered preview](/ui-library/components/tag/_previews/selection/)
````citry
import citry_ui
from citry import Component, citry
citry.register_library(citry_ui)
class TagSelection(Component):
template = """
chosen = value}"
>
Quiet
Bright
Central
"""
preview = TagSelection()
preview # noqa: B018
````
```citry-html
selectedAmenities = value
}"
>
Wi-Fi
Parking
Pool
```
A supplied client `value` is authoritative. The callback requests the next
selection; it does not mutate a controlled group. Omit the prop to release
control while preserving the last effective selection. `mandatory=True`
prevents user activation from clearing the final selection.
## Actions and removal
`actionable=True` reports enabled Tag activation through `onAction`.
`removable=True` adds one form-safe remove Button and enables Delete and
Backspace. Removal is a request: update your collection to remove the values.
### Request Tag removal
[Open the rendered preview](/ui-library/components/tag/_previews/removal/)
````citry
import citry_ui
from citry import Component, citry
citry.register_library(citry_ui)
class TagRemoval(Component):
template = """
last = values.join(', ')}"
>
Design
Research
Delivery
"""
preview = TagRemoval()
preview # noqa: B018
````
### Run Tag actions
[Open the rendered preview](/ui-library/components/tag/_previews/actions/)
````citry
import citry_ui
from citry import Component, citry
citry.register_library(citry_ui)
class TagActions(Component):
template = """
last = value}"
>
Overview
Activity
Settings
"""
preview = TagActions()
preview # noqa: B018
````
```citry-html
removeSavedFilters(values)
}"
>
Open
Assigned to me
```
When a selected Tag in multiple mode receives Delete, the request includes all
selected removable values. Focus follows retained values across reorder and
moves to the nearest following Tag after removal.
## Content
The default slot is the Tag label. `start` accepts decorative noninteractive
phrasing content such as an Icon or Avatar.
### Compose Tag content
[Open the rendered preview](/ui-library/components/tag/_previews/content/)
````citry
import citry_ui
from citry import Component, citry
citry.register_library(citry_ui)
class TagContent(Component):
template = """
A
Ava, accessibility research
L
Leo, design systems
"""
preview = TagContent()
preview # noqa: B018
````
```citry-html
A
Ava
```
Tag content must not contain links, Buttons, form controls, focusable content,
or nested Tags. Use a native anchor outside TagGroup when the job is
navigation. Free-form entry and editing belong to `CTagsInput`.
## Keyboard behavior
- Arrow keys move through enabled Tags and wrap.
- Home and End move to the first and last enabled Tag.
- Typing moves to the next matching Tag label or `text_value`.
- Enter and Space activate selection and actions.
- Delete and Backspace request removal.
- Tab from a removable Tag reaches its remove Button; Shift+Tab returns.
The group has one page-tab entry. Descriptive groups remain ordinary lists and
do not add keyboard stops.
## Disabledness and forms
Group disabledness, item disabledness, `CForm.disabled`, and native disabled
fieldsets all dominate interaction. TagGroup is not a form control and adds no
FormData. Owned remove Buttons always use `type="button"`.
## Presentation and customization
Variants are `soft`, `solid`, and `outline`. Sizes are `sm`, `md`, and `lg`.
Customize through public variables or stable part selectors:
### Compare Tag variants and sizes
[Open the rendered preview](/ui-library/components/tag/_previews/variants/)
````citry
import citry_ui
from citry import Component, citry
citry.register_library(citry_ui)
class TagVariants(Component):
template = """
Selected Available
Sample
"""
preview = TagVariants()
preview # noqa: B018
````
### Customize Tags
[Open the rendered preview](/ui-library/components/tag/_previews/customization/)
````citry
import citry_ui
from citry import Component, citry
citry.register_library(citry_ui)
class TagCustomization(Component):
css = """
:where(.forest-tags) {
--cui-tag-selected-background: #176b4d;
--cui-tag-selected-foreground: #fff;
--cui-tag-radius: 0.45rem;
}
"""
template = """
Fern
Moss
River
"""
preview = TagCustomization()
preview # noqa: B018
````
```css
.brand-tags {
--cui-tag-selected-background: #176b4d;
--cui-tag-selected-foreground: #fff;
--cui-tag-radius: 0.5rem;
}
```
See [`api.yml`](api.yml) for the exhaustive inputs, callbacks, variables,
attributes, selectors, slots, and public interfaces.
## API reference
### Inputs
#### CTagGroup server inputs
Server inputs are passed in a template through ` ` or in Python through
`CTagGroup(...)`.
| Input | Type | Default | Effect |
|---|---|---|---|
| `label` | `str` | required | Supplies the visible fallback label and accessible group name. |
| `id` | `str | None` | `None` | Supplies the exact root and relationship prefix. |
| `value` | `str | Sequence[str] | None` ([`CTagValue`](#tag-interface-value)) | `None` | Sets initial single or multiple selection. |
| `selection_mode` | `"none" | "single" | "multiple"` ([`CTagSelectionMode`](#tag-interface-selection-mode)) | `"none"` | Selects descriptive or selectable behavior. |
| `mandatory` | `bool` | `False` | Prevents activation from clearing the final selection. |
| `actionable` | `bool` | `False` | Enables Tag action callbacks. |
| `removable` | `bool` | `False` | Adds form-safe remove Buttons and deletion keys. |
| `remove_label` | `str` | `"Remove"` | Supplies the translated remove action label. |
| `disabled` | `bool` | `False` | Disables the owned collection; Form and fieldset disabledness remain dominant. |
| `variant` | `"soft" | "solid" | "outline"` ([`CTagVariant`](#tag-interface-variant)) | `"soft"` | Selects visual treatment. |
| `size` | `"sm" | "md" | "lg"` ([`CTagSize`](#tag-interface-size)) | `"md"` | Selects Tag geometry. |
| `class_` | `CClassValue | None` ([`CClassValue`](#tag-interface-class-value)) | `None` | Adds root classes. |
| `style` | `CStyleValue | None` ([`CStyleValue`](#tag-interface-style-value)) | `None` | Adds root inline styles. |
| `attrs` | `Mapping[str, object] | None` | `None` | Adds trusted root attributes without replacing owned semantics. |
#### CTagGroup client inputs
Client inputs are passed in the browser through the `$c-props="{ ... }"` attribute on
` `.
| Input | Type | Omitted behavior | Effect |
|---|---|---|---|
| `value` | `string | null | string[] | undefined` | Releases control and preserves the last effective selection. | Controls selection while supplied. |
| `disabled` | `boolean | undefined` | Uses the server fallback. | Overrides local disabledness while valid. |
| `variant` | `"soft" | "solid" | "outline" | undefined` | Uses the server fallback. | Overrides visual treatment while valid. |
| `size` | `"sm" | "md" | "lg" | undefined` | Uses the server fallback. | Overrides geometry while valid. |
| `onValueChange` | `((value, detail) => void) | undefined` | No selection notification. | Receives selection requests. |
| `onAction` | `((value, detail) => void) | undefined` | No action notification. | Receives enabled actionable Tag activation. |
| `onRemove` | `((values, detail) => void) | undefined` | No removal notification. | Receives remove Button or deletion-key requests. |
#### CTag server inputs
Server inputs are passed in a template through ` ` or in Python through
`CTag(...)`.
| Input | Type | Default | Effect |
|---|---|---|---|
| `value` | `str` | required | Supplies unique canonical identity within the group. |
| `disabled` | `bool` | `False` | Disables this Tag. |
| `text_value` | `str | None` | `None` | Supplies typeahead text instead of current label text. |
| `class_` | `CClassValue | None` ([`CClassValue`](#tag-interface-class-value)) | `None` | Adds Tag-root classes. |
| `style` | `CStyleValue | None` ([`CStyleValue`](#tag-interface-style-value)) | `None` | Adds Tag-root inline styles. |
| `attrs` | `Mapping[str, object] | None` | `None` | Adds trusted Tag-root attributes without replacing owned semantics. |
#### CTag client inputs
Client inputs are passed in the browser through the `$c-props="{ ... }"` attribute on
` `.
| Input | Type | Omitted behavior | Effect |
|---|---|---|---|
| `disabled` | `boolean | undefined` | Uses the server fallback. | Overrides item-local disabledness while valid. |
| `textValue` | `string | null | undefined` | Uses server text or current label text. | Overrides typeahead text while valid. |
### Slots
Slots are passed as nested content or `` tags in a template, or through the
`slots={...}` argument in Python.
#### CTagGroup slots
| Slot | Required | Data | Fallback |
|---|---|---|---|
| `default` | yes | `{}` ([`CTagGroupDefaultSlotData`](#tag-interface-group-default-slot)) | None. |
| `label` | no | `{}` ([`CTagGroupLabelSlotData`](#tag-interface-group-label-slot)) | Escaped label input. |
| `description` | no | `{}` ([`CTagGroupDescriptionSlotData`](#tag-interface-group-description-slot)) | Wrapper omitted. |
#### CTag slots
| Slot | Required | Data | Fallback |
|---|---|---|---|
| `default` | yes | `{}` ([`CTagDefaultSlotData`](#tag-interface-tag-default-slot)) | None. |
| `start` | no | `{}` ([`CTagStartSlotData`](#tag-interface-tag-start-slot)) | Wrapper omitted. |
### Events
Component events are callback inputs supplied through `$c-props`. Native browser events
remain available through Alpine `@...` attributes.
#### CTagGroup events
| Event | Signature | Trigger and timing | Detail | Controlled and cancellation behavior |
|---|---|---|---|---|
| `onValueChange` | `(value, detail: CTagValueChangeDetail) => void` ([`CTagValueChangeDetail`](#tag-interface-value-change-detail)) | Enabled selectable Tag proposes a different value. | `{value, previousValue, tagValue, source, controlled, nativeEvent}` ([`CTagValueChangeDetail`](#tag-interface-value-change-detail)) | Runs before onAction; supplied client value remains authoritative. |
| `onAction` | `(value: str, detail: CTagActionDetail) => void` ([`CTagActionDetail`](#tag-interface-action-detail)) | Enabled actionable Tag activates. | `{value, source, nativeEvent}` ([`CTagActionDetail`](#tag-interface-action-detail)) | Runs after a selection request. |
| `onRemove` | `(values: list[str], detail: CTagRemoveDetail) => void` ([`CTagRemoveDetail`](#tag-interface-remove-detail)) | Remove Button or Delete and Backspace. | `{values, tagValue, source, nativeEvent}` ([`CTagRemoveDetail`](#tag-interface-remove-detail)) | Requests owner collection removal without changing structure. |
### Methods
-
### CSS
CSS variables to theme the components. Set them on an ancestor or the component itself.
#### CTagGroup CSS variables
Apply these variables to `CTagGroup` or one of its ancestors.
| Variable | Type | Purpose | Default |
|---|---|---|---|
| `--cui-tag-gap` | `length` | Inline gap between Tags. | `0.5rem` |
| `--cui-tag-row-gap` | `length` | Gap between wrapped rows. | `0.5rem` |
| `--cui-tag-background` | `color` | Unselected fill. | `Variant and scheme derived.` |
| `--cui-tag-foreground` | `color` | Unselected text. | `Variant and scheme derived.` |
| `--cui-tag-border-color` | `color` | Tag border. | `Scheme-derived neutral.` |
| `--cui-tag-selected-background` | `color` | Selected fill. | `Scheme-derived primary.` |
| `--cui-tag-selected-foreground` | `color` | Selected text. | `White.` |
| `--cui-tag-selected-border-color` | `color` | Selected border. | `Selected background.` |
| `--cui-tag-focus-color` | `color` | Focus outline. | `Highlight` |
| `--cui-tag-radius` | `length` | Tag corner radius. | `999px` |
| `--cui-tag-min-height` | `length` | Minimum Tag block size. | `Size derived.` |
| `--cui-tag-padding-inline` | `length` | Tag inline padding. | `Size derived.` |
| `--cui-tag-internal-gap` | `length` | Gap between internal parts. | `Size derived.` |
| `--cui-tag-font-size` | `length` | Tag label size. | `Size derived.` |
| `--cui-tag-label-color` | `color` | Group-label foreground. | `CanvasText` |
| `--cui-tag-description-color` | `color` | Description foreground. | `Scheme-derived muted text.` |
### Attributes
HTML attributes defined on the components that you can refer to for CSS, inspection, and
testing. Read-only.
#### CTagGroup attributes
| Attribute | Element | Type | Meaning |
|---|---|---|---|
| `data-selection-mode` | Group root | `"none" | "single" | "multiple"` | Reflects collection behavior. |
| `data-actionable` | Group root | `present-or-absent` | Present when action callbacks are enabled. |
| `data-removable` | Group root | `present-or-absent` | Present when removal is enabled. |
| `data-disabled` | Group root | `present-or-absent` | Mirrors effective group disabledness. |
| `data-variant` | Group root and Tag | `"soft" | "solid" | "outline"` | Reflects visual treatment. |
| `data-size` | Group root and Tag | `"sm" | "md" | "lg"` | Reflects geometry. |
#### CTag attributes
| Attribute | Element | Type | Meaning |
|---|---|---|---|
| `data-value` | Tag root | `string` | Exposes canonical identity. |
| `data-selected` | Tag root | `present-or-absent` | Mirrors effective selection. |
| `data-disabled` | Tag root | `present-or-absent` | Mirrors effective item disabledness. |
| `data-removable` | Tag root | `present-or-absent` | Present when the remove affordance exists. |
| `aria-selected` | Selectable Tag row | `boolean` | Exposes selection to assistive technology. |
| `aria-disabled` | Interactive Tag row | `boolean` | Exposes effective disabledness. |
### Selectors
Selectors for the DOM nodes in the components that you can use for CSS, inspection, and
testing.
#### CTagGroup selectors
| Selector | Element | Purpose |
|---|---|---|
| `[data-citry-ui-part="tag-group"]` | Group root | Stable group and attrs destination. |
| `[data-citry-ui-part="group-label"]` | Visible group label | Names the collection. |
| `[data-citry-ui-part="list"]` | List or grid | Stable direct collection surface. |
| `[data-citry-ui-part="description"]` | Optional description | Describes the collection. |
| `[data-citry-ui-part="tag"]` | Tag root | Stable Tag and attrs destination. |
| `[data-citry-ui-part="indicator"]` | Selection indicator | Exposes selected state visually. |
| `[data-citry-ui-part="start"]` | Decorative start wrapper | Positions composed decoration. |
| `[data-citry-ui-part="tag-label"]` | Tag label | Supplies the accessible Tag name. |
| `[data-citry-ui-part="remove"]` | Native Button | Requests removal. |
### Interfaces
Aliases and data shapes referenced above.
#### Input type aliases
| Interface | Definition |
|---|---|
| `CClassValue` | `str | Mapping[str, bool] | Sequence[CClassValue]` |
| `CStyleValue` | `str | Mapping[str, str | int | float | bool | None] | Sequence[CStyleValue]` |
| `CTagSelectionMode` | `Literal["none", "single", "multiple"]` |
| `CTagVariant` | `Literal["soft", "solid", "outline"]` |
| `CTagSize` | `Literal["sm", "md", "lg"]` |
| `CTagValue` | `str | None | Sequence[str]` |
#### `CTagValueChangeDetail`
| Field | Type | Default | Meaning |
|---|---|---|---|
| `value` | `str | list[str] | None` | - | Requested selection. |
| `previousValue` | `str | list[str] | None` | - | Selection before activation. |
| `tagValue` | `str` | - | Activated Tag identity. |
| `source` | `"activation"` | - | Change origin. |
| `controlled` | `bool` | - | Whether client value controls selection. |
| `nativeEvent` | `Event` | - | Triggering native event. |
#### `CTagActionDetail`
| Field | Type | Default | Meaning |
|---|---|---|---|
| `value` | `str` | - | Activated Tag identity. |
| `source` | `"activation"` | - | Action origin. |
| `nativeEvent` | `Event` | - | Triggering native event. |
#### `CTagRemoveDetail`
| Field | Type | Default | Meaning |
|---|---|---|---|
| `values` | `list[str]` | - | Requested removal identities. |
| `tagValue` | `str` | - | Tag that received the removal action. |
| `source` | `"remove-button" | "delete-key"` | - | Removal origin. |
| `nativeEvent` | `Event` | - | Triggering native event. |
#### `CTagGroupDefaultSlotData`
Empty dataclass: `{}`.
#### `CTagGroupLabelSlotData`
Empty dataclass: `{}`.
#### `CTagGroupDescriptionSlotData`
Empty dataclass: `{}`.
#### `CTagDefaultSlotData`
Empty dataclass: `{}`.
#### `CTagStartSlotData`
Empty dataclass: `{}`.
### Translation keys
Catalog keys used by this family. An explicit component input or slot listed in Override
takes precedence over the catalog for that instance.
#### CTagGroup translation keys
| Key | Purpose | Variables | Override | Browser updates |
|---|---|---|---|---|
| `citry-ui-tag-remove` | Supplies hidden accessible text for every remove control. | `None` | `remove_label` input | $c-tr updates text content. |
---
# Timeline
Source: https://citry.dev/ui-library/components/timeline/
# Timeline
Use `CTimeline` and `CTimelineItem` for ordered histories, activity feeds,
roadmaps, and status sequences. Timeline is presentational: links, actions,
loading, and date formatting remain owned by your application.
## Timeline at a glance
### Timeline at a glance
[Open the rendered preview](/ui-library/components/timeline/_previews/at-a-glance/)
````citry
import citry_ui
from citry import Component, citry
citry.register_library(citry_ui)
class TimelineAtAGlance(Component):
template = """
18 Aug
Order confirmed Payment received
Today
In transit Departed the regional hub
Delivered
"""
preview = TimelineAtAGlance()
preview # noqa: B018
````
## Present an activity feed
Place semantic `` elements, headings, descriptions, links, and actions
inside each Item. The authored DOM order remains the reading order.
When any Item has opposite metadata, the whole vertical Timeline reserves one
consistent metadata column so the track never jumps between Items. Content on
the logical start side—including opposite time labels—is aligned toward the
track rather than toward the outside edge.
### Present an activity feed
[Open the rendered preview](/ui-library/components/timeline/_previews/activity/)
````citry
import citry_ui
from citry import Component, citry
citry.register_library(citry_ui)
class TimelineActivity(Component):
template = """
09:15
Mina opened pull request #184 Improve invoice import diagnostics.
10:04
Leo approved the changes All required checks passed.
10:12
Ready to merge Review the final diff
"""
css = ":where(.cui-timeline__content p){margin:.25rem 0 0}"
preview = TimelineActivity()
preview # noqa: B018
````
## Communicate status in text
Item `state` styles the indicator. It never replaces a written status: the
indicator is decorative, and only one Item may be `current`.
### Present status history
[Open the rendered preview](/ui-library/components/timeline/_previews/status/)
````citry
import citry_ui
from citry import Component, citry
citry.register_library(citry_ui)
class TimelineStatus(Component):
template = """
Build completed Artifacts signed successfully
Staging failed Health check timed out
Retry in progress Current attempt is running
Production pending Waiting for staging approval
"""
preview = TimelineStatus()
preview # noqa: B018
````
## Alternate content around the track
Use `side="alternate"` for a centered vertical track. An Item can override its
resolved side with `side="start"` or `side="end"`. All Items retain the same
three-column geometry even when only some of them provide opposite content.
### Build an alternating Timeline
[Open the rendered preview](/ui-library/components/timeline/_previews/alternating/)
````citry
import citry_ui
from citry import Component, citry
citry.register_library(citry_ui)
class TimelineAlternating(Component):
template = """
Prototype The first field trial validated the core workflow.
Private beta Design partners shaped the collaboration model.
Public beta The current release focuses on reliability and polish.
General availability Operational review and migration guidance remain.
"""
css = ":where(.cui-timeline__content p){margin:.25rem 0 0}"
preview = TimelineAlternating()
preview # noqa: B018
````
## Build a horizontal roadmap
Horizontal Timelines preserve chronological DOM order, share one Grid Row for
the complete connector, and scroll within their own bounds at narrow widths.
### Build a horizontal roadmap
[Open the rendered preview](/ui-library/components/timeline/_previews/horizontal/)
````citry
import citry_ui
from citry import Component, citry
citry.register_library(citry_ui)
class TimelineHorizontal(Component):
template = """
Q1 Unified accounts
Q2 Regional storage
Q3 Audit workspaces
Q4 Policy automation
"""
preview = TimelineHorizontal()
preview # noqa: B018
````
## Customize indicators and the track
Use the `indicator` slot for an icon, avatar, or authored marker and public CSS
variables for geometry and color. Indicator content is hidden from assistive
technology, so repeat its meaning in the Item's visible content.
### Customize Timeline
[Open the rendered preview](/ui-library/components/timeline/_previews/customization/)
````citry
import citry_ui
from citry import Component, citry
citry.register_library(citry_ui)
class TimelineCustomization(Component):
template = """
AK
Ada assigned the issue Ownership moved to Platform.
JM
Jules is investigating Current work is linked in the incident log.
"""
css = """
:where(.custom-timeline) { --cui-timeline-current-color:#7c3aed; }
:where(.custom-timeline .avatar) {
display:grid;
inline-size:100%;
block-size:100%;
place-items:center;
border-radius:50%;
background:currentcolor;
color:Canvas;
font-size:.65rem;
font-weight:800;
}
"""
preview = TimelineCustomization()
preview # noqa: B018
````
## Timeline or Stepper?
Use Timeline to read events or history. Use Stepper when the user is moving
through a finite workflow and the component owns a current step or optional
step navigation.
## Accessibility and localization
Timeline renders one ordered list with one list item per event. It adds no
focus target or Arrow-key behavior. An Item with `state="current"` receives
`aria-current="true"`; all other state meaning must be written in content.
Timeline owns no text or date formatting and therefore has no catalog keys.
Author localized content with ordinary Citry `tr()` or `$c-tr`, render dates
with your application's locale profile, and add explicit `dir` boundaries when
mixing directional content.
## API reference
### Inputs
#### CTimeline server inputs
Server inputs are passed in a template through ` ` or in Python through
`CTimeline(...)`.
| Input | Type | Default | Effect |
|---|---|---|---|
| `orientation` | `CTimelineOrientation` ([`CTimelineOrientation`](#timeline-interface-orientation)) | `"vertical"` | Selects a vertical or horizontal track axis. |
| `side` | `CTimelineSide` ([`CTimelineSide`](#timeline-interface-side)) | `"end"` | Places Item content at the logical end start or alternating sides of the track. |
| `line_style` | `CTimelineLineStyle` ([`CTimelineLineStyle`](#timeline-interface-line-style)) | `"solid"` | Selects solid or dashed connectors. |
| `density` | `CTimelineDensity` ([`CTimelineDensity`](#timeline-interface-density)) | `"comfortable"` | Selects comfortable or compact spacing. |
| `size` | `CTimelineSize` ([`CTimelineSize`](#timeline-interface-size)) | `"md"` | Selects coordinated indicator and track geometry. |
| `label` | `str | None` | `None` | Optionally supplies the ordered list accessible name. |
| `class_` | `CClassValue | None` ([`CClassValue`](#timeline-interface-class-value)) | `None` | Adds classes to the root ordered list. |
| `style` | `CStyleValue | None` ([`CStyleValue`](#timeline-interface-style-value)) | `None` | Adds styles to the root ordered list. |
| `attrs` | `Mapping[str, object] | None` | `None` | Adds copied allowed attributes without replacing owned semantics or state. |
#### CTimelineItem server inputs
Server inputs are passed in a template through ` ` or in Python
through `CTimelineItem(...)`.
| Input | Type | Default | Effect |
|---|---|---|---|
| `state` | `CTimelineState` ([`CTimelineState`](#timeline-interface-state)) | `"neutral"` | Styles authored neutral complete current pending or error status; current adds aria-current. |
| `side` | `CTimelineItemSide` ([`CTimelineItemSide`](#timeline-interface-item-side)) | `"auto"` | Uses the root-resolved side or overrides one Item to logical start or end. |
| `class_` | `CClassValue | None` ([`CClassValue`](#timeline-interface-class-value)) | `None` | Adds classes to the rendered list item. |
| `style` | `CStyleValue | None` ([`CStyleValue`](#timeline-interface-style-value)) | `None` | Adds styles to the rendered list item. |
| `attrs` | `Mapping[str, object] | None` | `None` | Adds copied allowed list-item attributes without replacing owned semantics or state. |
### Slots
Slots are passed as nested content or `` tags in a template, or through the
`slots={...}` argument in Python.
#### CTimeline slots
| Slot | Required | Data | Fallback |
|---|---|---|---|
| `default` | yes | `{}` ([`CTimelineDefaultSlotData`](#timeline-interface-ctimeline-default-slot-data)) | None; one or more CTimelineItem declarations are required. |
#### CTimelineItem slots
| Slot | Required | Data | Fallback |
|---|---|---|---|
| `default` | yes | `{index, state, side, is_first, is_last}` ([`CTimelineItemDefaultSlotData`](#timeline-interface-ctimeline-item-default-slot-data)) | None. |
| `opposite` | no | `{index, state, side, is_first, is_last}` ([`CTimelineItemOppositeSlotData`](#timeline-interface-opposite-slot-data)) | Omitted. |
| `indicator` | no | `{index, state, side, is_first, is_last}` ([`CTimelineItemIndicatorSlotData`](#timeline-interface-indicator-slot-data)) | Decorative dot. |
### Events
-
### Methods
-
### CSS
CSS variables to theme the components. Set them on an ancestor or the component itself.
#### CTimeline CSS variables
Apply these variables to `CTimeline` or one of its ancestors.
| Variable | Type | Purpose | Default |
|---|---|---|---|
| `--cui-timeline-gap` | `length` | Minimum space along the sequence axis. | `1.5rem; compact 0.75rem` |
| `--cui-timeline-item-gap` | `length` | Space between the track and authored content. | `0.75rem; compact 0.5rem` |
| `--cui-timeline-track-size` | `length` | Cross-axis track lane size. | `sm 1.5rem; md 2rem; lg 2.5rem` |
| `--cui-timeline-indicator-size` | `length` | Indicator inline and block size. | `sm 0.5rem; md 0.75rem; lg 1rem` |
| `--cui-timeline-line-width` | `length` | Connector and indicator border thickness. | `0.125rem` |
| `--cui-timeline-line-color` | `color` | Connector color. | `Adaptive neutral` |
| `--cui-timeline-indicator-color` | `color` | Neutral indicator color. | `Adaptive neutral` |
| `--cui-timeline-current-color` | `color` | Current indicator color. | `Adaptive blue` |
| `--cui-timeline-complete-color` | `color` | Complete indicator color. | `Adaptive green` |
| `--cui-timeline-pending-color` | `color` | Pending indicator color. | `Adaptive muted neutral` |
| `--cui-timeline-error-color` | `color` | Error indicator color. | `Adaptive red` |
| `--cui-timeline-muted-color` | `color` | Opposite metadata color. | `Adaptive neutral` |
### Attributes
HTML attributes defined on the components that you can refer to for CSS, inspection, and
testing. Read-only.
#### CTimeline attributes
| Attribute | Element | Type | Meaning |
|---|---|---|---|
| `aria-label` | Root ol | `string` | Optional accessible name. |
| `data-orientation` | Root ol | `CTimelineOrientation` ([`CTimelineOrientation`](#timeline-interface-orientation)) | Mirrors the track axis. |
| `data-side` | Root ol | `CTimelineSide` ([`CTimelineSide`](#timeline-interface-side)) | Mirrors root placement policy. |
| `data-line-style` | Root ol | `CTimelineLineStyle` ([`CTimelineLineStyle`](#timeline-interface-line-style)) | Mirrors connector treatment. |
| `data-density` | Root ol | `CTimelineDensity` ([`CTimelineDensity`](#timeline-interface-density)) | Mirrors spacing density. |
| `data-size` | Root ol | `CTimelineSize` ([`CTimelineSize`](#timeline-interface-size)) | Mirrors geometry size. |
| `data-has-opposite` | Root ol | `present | absent` | Reserves one consistent metadata column when any Item has opposite content. |
#### CTimelineItem attributes
| Attribute | Element | Type | Meaning |
|---|---|---|---|
| `data-index` | Item li | `nonnegative-integer-string` | Exposes settled zero-based order. |
| `data-state` | Item li | `CTimelineState` ([`CTimelineState`](#timeline-interface-state)) | Mirrors authored visual status. |
| `data-side` | Item li | `start | end` | Mirrors resolved logical placement. |
| `data-has-opposite` | Item li | `present | absent` | Marks an authored opposite slot. |
| `aria-current` | Current Item li | `true` | Identifies the one current event. |
### Selectors
Selectors for the DOM nodes in the components that you can use for CSS, inspection, and
testing.
#### CTimeline selectors
| Selector | Element | Purpose |
|---|---|---|
| `[data-citry-ui-part="timeline"]` | Root ol | State reflections and root customization destination. |
| `[data-citry-ui-part="item"]` | Item li | Item attrs state and customization destination. |
| `[data-citry-ui-part="opposite"]` | Optional metadata div | Dates and other opposite content. |
| `[data-citry-ui-part="track"]` | Decorative div | Owns connector segments and indicator. |
| `[data-citry-ui-part="before"]` | Decorative span | Connector segment before the indicator. |
| `[data-citry-ui-part="indicator"]` | Decorative span | Default dot or custom indicator destination. |
| `[data-citry-ui-part="after"]` | Decorative span | Connector segment after the indicator. |
| `[data-citry-ui-part="content"]` | Content div | Authored event content destination. |
### Interfaces
Aliases and data shapes referenced above.
#### Input type aliases
| Interface | Definition |
|---|---|
| `CTimelineOrientation` | `Literal["vertical", "horizontal"]` |
| `CTimelineSide` | `Literal["start", "end", "alternate"]` |
| `CTimelineItemSide` | `Literal["auto", "start", "end"]` |
| `CTimelineLineStyle` | `Literal["solid", "dashed"]` |
| `CTimelineDensity` | `Literal["comfortable", "compact"]` |
| `CTimelineSize` | `Literal["sm", "md", "lg"]` |
| `CTimelineState` | `Literal["neutral", "complete", "current", "pending", "error"]` |
| `CClassValue` | `str | Mapping[str, bool] | Sequence[CClassValue]` |
| `CStyleValue` | `str | Mapping[str, object] | Sequence[CStyleValue]` |
#### `CTimelineDefaultSlotData`
Empty dataclass: `{}`.
#### `CTimelineItemDefaultSlotData`
| Field | Type | Default | Meaning |
|---|---|---|---|
| `index` | `int` | - | Settled zero-based Item index. |
| `state` | `CTimelineState` ([`CTimelineState`](#timeline-interface-state)) | - | Authored Item state. |
| `side` | `start | end` | - | Resolved logical content side. |
| `is_first` | `bool` | - | Whether this is the first Item. |
| `is_last` | `bool` | - | Whether this is the last Item. |
#### Input type aliases
| Interface | Definition |
|---|---|
| `CTimelineItemOppositeSlotData` | `CTimelineItemDefaultSlotData` |
#### Input type aliases
| Interface | Definition |
|---|---|
| `CTimelineItemIndicatorSlotData` | `CTimelineItemDefaultSlotData` |
### Translation keys
-
---
# Tree
Source: https://citry.dev/ui-library/components/tree/
# Tree
Use `CTree` for compact hierarchical application data such as files or object
structures. Use disclosure navigation for ordinary site links.
## Tree at a glance
### Tree at a glance
[Open the rendered preview](/ui-library/components/tree/_previews/at-a-glance/)
````citry
import citry_ui
from citry import Component, citry
citry.register_library(citry_ui)
class TreeAtAGlance(Component):
template = """
"""
preview = TreeAtAGlance()
preview # noqa: B018
````
## Control expansion
### Control expanded branches
[Open the rendered preview](/ui-library/components/tree/_previews/controlled-expansion/)
````citry
import citry_ui
from citry import Component, citry
citry.register_library(citry_ui)
class ControlledExpansion(Component):
template = """
"""
preview = ControlledExpansion()
preview # noqa: B018
````
## Select one Item
### Select one Item
[Open the rendered preview](/ui-library/components/tree/_previews/single-selection/)
````citry
import citry_ui
from citry import Component, citry
citry.register_library(citry_ui)
class TreeSingleSelection(Component):
template = """
"""
preview = TreeSingleSelection()
preview # noqa: B018
````
## Select multiple Items
### Select multiple Items
[Open the rendered preview](/ui-library/components/tree/_previews/multiple-selection/)
````citry
import citry_ui
from citry import Component, citry
citry.register_library(citry_ui)
class TreeMultipleSelection(Component):
template = """
"""
preview = TreeMultipleSelection()
preview # noqa: B018
````
## Navigate with the keyboard
Down and Up move through visible Items. Right expands or enters a branch;
Left collapses or returns to its parent. Home, End, and buffered
typeahead follow the ARIA Tree pattern.
### Navigate a Tree
[Open the rendered preview](/ui-library/components/tree/_previews/keyboard/)
````citry
import citry_ui
from citry import Component, citry
citry.register_library(citry_ui)
class KeyboardTree(Component):
template = """
"""
preview = KeyboardTree()
preview # noqa: B018
````
## Disable Items
### Disable Tree Items
[Open the rendered preview](/ui-library/components/tree/_previews/disabled/)
````citry
import citry_ui
from citry import Component, citry
citry.register_library(citry_ui)
class TreeDisabledItems(Component):
template = """
"""
preview = TreeDisabledItems()
preview # noqa: B018
````
## Customize Tree
### Customize Tree
[Open the rendered preview](/ui-library/components/tree/_previews/customization/)
````citry
import citry_ui
from citry import Component, citry
citry.register_library(citry_ui)
class CustomizedTree(Component):
template = """
"""
preview = CustomizedTree()
preview # noqa: B018
````
## Accessibility and behavior
The named root uses `role="tree"`; Items use `role="treeitem"` and nested
children use `role="group"`. One visible Item is in the Tab order. Expansion,
selection, focus, and application action are separate states. Space selects,
Enter selects and invokes `onAction`, and double-click invokes the action.
## API reference
### Inputs
#### CTree server inputs
Server inputs are passed in a template through ` ` or in Python through
`CTree(...)`.
| Input | Type | Default | Effect |
|---|---|---|---|
| `label` | `str` | required | Names the Tree widget. |
| `expanded` | `Sequence[str]` | () | Sets initially expanded branch values. |
| `selected` | `Sequence[str]` | () | Sets initially selected Item values. |
| `selection_mode` | `"none" | "single" | "multiple"` ([`CTreeSelectionMode`](#tree-interface-ctree-selection-mode)) | `"single"` | Selects no-selection single-selection or independent multi-selection behavior. |
| `disabled` | `bool` | `False` | Disables expansion selection and action throughout the Tree. |
| `variant` | `"plain" | "soft" | "outline"` ([`CTreeVariant`](#tree-interface-ctree-variant)) | `"plain"` | Selects surface treatment. |
| `size` | `"sm" | "md" | "lg"` ([`CTreeSize`](#tree-interface-ctree-size)) | `"md"` | Selects row and indentation geometry. |
| `class_` | `CClassValue | None` ([`CClassValue`](#tree-interface-ctree-class-value)) | `None` | Adds root classes. |
| `style` | `CStyleValue | None` ([`CStyleValue`](#tree-interface-ctree-style-value)) | `None` | Adds root inline styles. |
| `attrs` | `Mapping[str, object] | None` | `None` | Adds trusted root attributes without replacing owned semantics focus state structure or runtime. |
#### CTree client inputs
Client inputs are passed in the browser through the `$c-props="{ ... }"` attribute on
` `.
| Input | Type | Omitted behavior | Effect |
|---|---|---|---|
| `expanded` | `string[] | null` | Uses uncontrolled committed expansion. | Controls expanded branches while supplied; null releases control. |
| `selected` | `string[] | null` | Uses uncontrolled committed selection. | Controls selected Items while supplied; null releases control. |
| `selectionMode` | `"none" | "single" | "multiple"` ([`CTreeSelectionMode`](#tree-interface-ctree-selection-mode)) | Uses the server value. | Reactively changes selection behavior. |
| `disabled` | `bool` | Uses the server value. | Reactively disables Tree operations. |
| `variant` | `"plain" | "soft" | "outline"` ([`CTreeVariant`](#tree-interface-ctree-variant)) | Uses the server value. | Reactively changes presentation. |
| `size` | `"sm" | "md" | "lg"` ([`CTreeSize`](#tree-interface-ctree-size)) | Uses the server value. | Reactively changes geometry. |
| `onExpandedChange` | `((expanded: string[], detail: CTreeExpandedChangeDetail) => void) | undefined` | No component callback runs. | Receives branch expansion requests. |
| `onSelectionChange` | `((selected: string[], detail: CTreeSelectionChangeDetail) => void) | undefined` | No component callback runs. | Receives Item selection requests. |
| `onAction` | `((value: string, detail: CTreeActionDetail) => void) | undefined` | No component callback runs. | Receives enabled Enter or double-click actions. |
#### CTreeItem server inputs
Server inputs are passed in a template through ` ` or in Python through
`CTreeItem(...)`.
| Input | Type | Default | Effect |
|---|---|---|---|
| `value` | `str` | required | Supplies stable unique Item identity. |
| `label` | `str` | required | Supplies visible text accessible naming and typeahead text. |
| `disabled` | `bool` | `False` | Keeps the Item focusable by Tree navigation but prevents operations. |
| `class_` | `CClassValue | None` ([`CClassValue`](#tree-interface-ctree-class-value)) | `None` | Adds classes to the concrete Item. |
| `style` | `CStyleValue | None` ([`CStyleValue`](#tree-interface-ctree-style-value)) | `None` | Adds inline styles to the concrete Item. |
| `attrs` | `Mapping[str, object] | None` | `None` | Adds trusted Item attributes without replacing owned semantics identity focus state or children. |
### Slots
Slots are passed as nested content or `` tags in a template, or through the
`slots={...}` argument in Python.
#### CTree slots
| Slot | Required | Data | Fallback |
|---|---|---|---|
| `default` | yes | `{}` ([`CTreeDefaultSlotData`](#tree-interface-ctree-default-slot-data)) | None. Requires one or more direct CTreeItem declarations. |
#### CTreeItem slots
| Slot | Required | Data | Fallback |
|---|---|---|---|
| `default` | no | `{parent_value, level}` ([`CTreeItemDefaultSlotData`](#tree-interface-ctree-item-default-slot-data)) | Omitted for a leaf; otherwise accepts child CTreeItem declarations only. |
### Events
Component events are callback inputs supplied through `$c-props`. Native browser events
remain available through Alpine `@...` attributes.
#### CTree events
| Event | Signature | Trigger and timing | Detail | Controlled and cancellation behavior |
|---|---|---|---|---|
| `onExpandedChange` | `(expanded: string[], detail: CTreeExpandedChangeDetail) => void` ([`CTreeExpandedChangeDetail`](#tree-interface-ctree-expanded-change-detail)) | Enabled pointer-indicator or keyboard branch request. | `{value, expanded, previousExpanded, controlled, source, item, sourceEvent}` ([`CTreeExpandedChangeDetail`](#tree-interface-ctree-expanded-change-detail)) | Commits immediately when uncontrolled and waits when controlled. |
| `onSelectionChange` | `(selected: string[], detail: CTreeSelectionChangeDetail) => void` ([`CTreeSelectionChangeDetail`](#tree-interface-ctree-selection-change-detail)) | Enabled row click Space or Enter in a selectable mode. | `{value, selected, previousSelected, controlled, source, item, sourceEvent}` ([`CTreeSelectionChangeDetail`](#tree-interface-ctree-selection-change-detail)) | Applies single or independent multiple selection policy. |
| `onAction` | `(value: string, detail: CTreeActionDetail) => void` ([`CTreeActionDetail`](#tree-interface-ctree-action-detail)) | Enabled Enter or double-click. | `{value, item, sourceEvent}` ([`CTreeActionDetail`](#tree-interface-ctree-action-detail)) | Notifies application action without navigation or form submission. |
### Methods
-
### CSS
CSS variables to theme the components. Set them on an ancestor or the component itself.
#### CTree CSS variables
Apply these variables to `CTree` or one of its ancestors.
| Variable | Type | Purpose | Default |
|---|---|---|---|
| `--cui-tree-indent` | `length` | Logical child indentation. | `sm 1rem; md 1.25rem; lg 1.5rem` |
| `--cui-tree-row-gap` | `length` | Gap between sibling rows. | `0.125rem` |
| `--cui-tree-row-padding` | `length` | Row block and inline padding. | `size-derived` |
| `--cui-tree-radius` | `length` | Root and row corner radius. | `0.5rem` |
| `--cui-tree-background` | `color` | Root background. | `plain and outline transparent; soft subtle CanvasText mix` |
| `--cui-tree-border-color` | `color` | Outline border. | `light #d0d5dd; dark #535862` |
| `--cui-tree-hover-background` | `color` | Enabled row hover background. | `7% CanvasText mix` |
| `--cui-tree-selected-background` | `color` | Selected row background. | `light #dbeafe; dark #1e3a5f` |
| `--cui-tree-selected-color` | `color` | Selected row foreground. | `light #1849a9; dark #d1e9ff` |
| `--cui-tree-muted-color` | `color` | Disabled Item foreground. | `light #667085; dark #a4a7ae` |
| `--cui-tree-focus-color` | `color` | Roving focus outline. | `Highlight` |
### Attributes
HTML attributes defined on the components that you can refer to for CSS, inspection, and
testing. Read-only.
#### CTree attributes
| Attribute | Element | Type | Meaning |
|---|---|---|---|
| `role` | Root or Item/group div | `tree | treeitem | group` | Owns Tree hierarchy semantics. |
| `aria-label` | Root or Item div | `string` | Names the Tree and each Item. |
| `tabindex` | Item div | `0 | -1` | Implements one roving visible Tab stop. |
| `aria-disabled` | Item div | `true | false` | Reflects effective Item unavailability. |
| `aria-expanded` | Branch Item div | `true | false` | Reflects branch visibility; omitted on leaves. |
| `aria-selected` | Selectable Item div | `true | false` | Reflects selection; omitted in none mode. |
| `data-selection-mode` | Root div | `none | single | multiple` | Mirrors effective selection model. |
| `data-disabled` | Root or Item div | `present-or-absent` | Reflects effective unavailability. |
| `data-variant` | Root div | `plain | soft | outline` | Mirrors effective presentation. |
| `data-size` | Root div | `sm | md | lg` | Mirrors effective geometry. |
| `data-value` | Item div | `string` | Exposes canonical Item identity. |
| `data-level` | Item div | `positive-integer-string` | Exposes settled hierarchy depth. |
| `data-expanded` | Branch Item div | `present-or-absent` | Present while expanded. |
| `data-selected` | Item div | `present-or-absent` | Present while selected. |
| `hidden` | Child group div | `present-or-absent` | Removes collapsed descendants from rendering. |
| `inert` | Child group div | `present-or-absent` | Guards collapsed descendants from interaction. |
### Selectors
Selectors for the DOM nodes in the components that you can use for CSS, inspection, and
testing.
#### CTree selectors
| Selector | Element | Purpose |
|---|---|---|
| `[data-citry-ui-part="tree"]` | Root div | Stable root and attrs destination. |
| `[data-citry-ui-part="item"]` | Tree Item div | Stable Item attrs and state surface. |
| `[data-citry-ui-part="row"]` | Row span | Stable visible Item surface. |
| `[data-citry-ui-part="indicator"]` | Decorative span | Pointer expansion target and branch indicator. |
| `[data-citry-ui-part="label"]` | Label span | Stable visible and typeahead text. |
| `[data-citry-ui-part="group"]` | Child group div | Stable nested collection and visibility surface. |
### Interfaces
Aliases and data shapes referenced above.
#### Input type aliases
| Interface | Definition |
|---|---|
| `CClassValue` | `str | Mapping[str, bool] | Sequence[CClassValue]` |
| `CStyleValue` | `str | Mapping[str, object] | Sequence[CStyleValue]` |
| `CTreeSelectionMode` | `Literal["none", "single", "multiple"]` |
| `CTreeVariant` | `Literal["plain", "soft", "outline"]` |
| `CTreeSize` | `Literal["sm", "md", "lg"]` |
| `CTreeChangeSource` | `Literal["pointer", "keyboard", "structure"]` |
#### `CTreeDefaultSlotData`
Empty dataclass: `{}`.
#### `CTreeItemDefaultSlotData`
| Field | Type | Default | Meaning |
|---|---|---|---|
| `parent_value` | `str` | - | Canonical parent Item identity. |
| `level` | `int` | - | One-based child hierarchy level. |
#### `CTreeExpandedChangeDetail`
| Field | Type | Default | Meaning |
|---|---|---|---|
| `value` | `str` | - | Changed branch identity. |
| `expanded` | `bool` | - | Requested branch state. |
| `previousExpanded` | `string[]` | - | Prior vector. |
| `controlled` | `bool` | - | Whether client expanded controls state. |
| `source` | `"pointer" | "keyboard" | "structure"` ([`CTreeChangeSource`](#tree-interface-ctree-change-source)) | - | Request source. |
| `item` | `HTMLElement` | - | Changed Item. |
| `sourceEvent` | `Event` | - | Native source event. |
#### `CTreeSelectionChangeDetail`
| Field | Type | Default | Meaning |
|---|---|---|---|
| `value` | `str` | - | Changed Item identity. |
| `selected` | `bool` | - | Requested selection state. |
| `previousSelected` | `string[]` | - | Prior vector. |
| `controlled` | `bool` | - | Whether client selected controls state. |
| `source` | `"pointer" | "keyboard" | "structure"` ([`CTreeChangeSource`](#tree-interface-ctree-change-source)) | - | Request source. |
| `item` | `HTMLElement` | - | Changed Item. |
| `sourceEvent` | `Event` | - | Native source event. |
#### `CTreeActionDetail`
| Field | Type | Default | Meaning |
|---|---|---|---|
| `value` | `str` | - | Activated Item identity. |
| `item` | `HTMLElement` | - | Activated Item. |
| `sourceEvent` | `Event` | - | Native Enter or double-click event. |
### Translation keys
-
---
# Tree Grid
Source: https://citry.dev/ui-library/components/tree-grid/
# Tree Grid
`CTreeGrid` combines a finite Row hierarchy with Data Grid columns. It is for
account trees, threaded records, work breakdowns, and similar structured data,
not spreadsheet formulas or inline editing.
### Present an account hierarchy
[Open the rendered preview](/ui-library/components/tree-grid/_previews/at-a-glance/)
````citry
# ruff: noqa: ANN001, ANN201 - public snippets keep focus on component use
import citry_ui
from citry import Component, citry
from citry_ui import CTreeGridColumn, CTreeGridRow
citry.register_library(citry_ui)
COLUMNS = [CTreeGridColumn("name", "Account", width=240), CTreeGridColumn("owner", "Owner")]
ROWS = [
CTreeGridRow(
"north",
"Northern region",
{"name": "Northern region", "owner": "Ada"},
children=[
CTreeGridRow("prague", "Prague", {"name": "Prague", "owner": "Mira"}),
CTreeGridRow("berlin", "Berlin", {"name": "Berlin", "owner": "Noah"}),
],
)
]
class TreeGridAtAGlance(Component):
def template_data(self, _kwargs, _slots):
return {"columns": COLUMNS, "rows": ROWS}
template = ' '
preview = TreeGridAtAGlance()
preview # noqa: B018
````
## Expand nested Rows
Put child `CTreeGridRow` records in `children` and list initially open branch
keys in `expanded`. The first Column owns indentation and expansion.
### Control visible project levels
[Open the rendered preview](/ui-library/components/tree-grid/_previews/expansion/)
````citry
# ruff: noqa: ANN001, ANN201 - public snippets keep focus on component use
import citry_ui
from citry import Component, citry
from citry_ui import CTreeGridColumn, CTreeGridRow
citry.register_library(citry_ui)
class TreeGridExpansion(Component):
def template_data(self, _kwargs, _slots):
return {
"columns": [CTreeGridColumn("work", "Work item", 260), CTreeGridColumn("state", "State")],
"rows": [
CTreeGridRow(
"launch",
"Launch",
{"work": "Launch", "state": "Active"},
children=[
CTreeGridRow("design", "Design", {"work": "Design", "state": "Done"}),
CTreeGridRow("build", "Build", {"work": "Build", "state": "Active"}),
],
)
],
}
template = ' '
preview = TreeGridExpansion()
preview # noqa: B018
````
## Select and submit Rows
Choose `single` or `multiple` selection and set `name` to emit repeated hidden
Row keys in preorder. Shift+Space toggles the focused Row, including unselect.
### Select organization units
[Open the rendered preview](/ui-library/components/tree-grid/_previews/selection/)
````citry
# ruff: noqa: ANN001, ANN201, E501 - public template stays readable
import citry_ui
from citry import Component, citry
from citry_ui import CTreeGridColumn, CTreeGridRow
citry.register_library(citry_ui)
class TreeGridSelection(Component):
def template_data(self, _kwargs, _slots):
return {
"columns": [CTreeGridColumn("team", "Team"), CTreeGridColumn("people", "People")],
"rows": [
CTreeGridRow("product", "Product", {"team": "Product", "people": 18}),
CTreeGridRow("ops", "Operations", {"team": "Operations", "people": 12}),
],
}
template = ''
preview = TreeGridSelection()
preview # noqa: B018
````
## Own state in Alpine
Client `expanded` and `selected` props are controlled. Their callbacks report
the requested vector, previous vector, Row key, requested boolean state,
controlled flag, source, and native event.
### Own expansion and selection
[Open the rendered preview](/ui-library/components/tree-grid/_previews/controlled/)
````citry
# ruff: noqa: ANN001, ANN201, E501 - public template stays readable
import citry_ui
from citry import Component, citry
from citry_ui import CTreeGridColumn, CTreeGridRow
citry.register_library(citry_ui)
class TreeGridControlled(Component):
def template_data(self, _kwargs, _slots):
return {
"columns": [CTreeGridColumn("name", "Name")],
"rows": [
CTreeGridRow(
"root", "Root", {"name": "Root"}, children=[CTreeGridRow("child", "Child", {"name": "Child"})]
)
],
}
template = """open=value,onSelectionChange:value=>chosen=value}" />
"""
preview = TreeGridControlled()
preview # noqa: B018
````
## Customize cells
Use `header`, `cell`, `toolbar`, and `caption` slots. Cell navigation stays on
the gridcell; interactive editing remains the separate Data Grid contract.
### Format hierarchical metrics
[Open the rendered preview](/ui-library/components/tree-grid/_previews/custom-cells/)
````citry
# ruff: noqa: ANN001, ANN201, E501 - public template stays readable
import citry_ui
from citry import Component, citry
from citry_ui import CTreeGridColumn, CTreeGridRow
citry.register_library(citry_ui)
class TreeGridCustomCells(Component):
def template_data(self, _kwargs, _slots):
return {
"columns": [CTreeGridColumn("name", "Initiative", 240), CTreeGridColumn("score", "Score", align="end")],
"rows": [
CTreeGridRow(
"quality",
"Quality",
{"name": "Quality", "score": 92},
children=[CTreeGridRow("a11y", "Accessibility", {"name": "Accessibility", "score": 98})],
)
],
}
template = """{{ cell.value }}% {{ cell.value }} """
preview = TreeGridCustomCells()
preview # noqa: B018
````
## Navigate accessibly
Arrow keys move through visible Rows and Columns. Left and Right also collapse,
expand, and return to parents from the hierarchy cell. Disabled Rows remain
readable but cannot mutate or activate.
### Keep focus and selection distinct
[Open the rendered preview](/ui-library/components/tree-grid/_previews/accessibility/)
````citry
# ruff: noqa: ANN001, ANN201 - public snippets keep focus on component use
import citry_ui
from citry import Component, citry
from citry_ui import CTreeGridColumn, CTreeGridRow
citry.register_library(citry_ui)
class TreeGridAccessibility(Component):
def template_data(self, _kwargs, _slots):
return {
"columns": [CTreeGridColumn("name", "Record", 260), CTreeGridColumn("status", "Status")],
"rows": [
CTreeGridRow("available", "Available record", {"name": "Available record", "status": "Ready"}),
CTreeGridRow(
"locked", "Locked record", {"name": "Locked record", "status": "Archived"}, disabled=True
),
],
}
template = (
' '
)
preview = TreeGridAccessibility()
preview # noqa: B018
````
Hierarchical sorting, async children, virtual Rows, and editing are explicit
future or adjacent contracts, not hidden Tree Grid modes.
## API reference
### Inputs
#### CTreeGrid server inputs
Server inputs are passed in a template through ` ` or in Python through
`CTreeGrid(...)`.
| Input | Type | Default | Effect |
|---|---|---|---|
| `columns` | `Sequence[CTreeGridColumn]` | required | Defines ordered aligned Columns; the first owns hierarchy controls. |
| `rows` | `Sequence[CTreeGridRow]` | required | Defines a finite recursive Row hierarchy. |
| `label` | `str` | required | Names the treegrid. |
| `id` | `str | None` | generated | Sets the root ID. |
| `expanded` | `Sequence[str]` | `"()"` | Supplies initially expanded branch Row keys. |
| `selection` | `CTreeGridSelection` ([`CTreeGridSelection`](#tree-grid-interface-selection)) | `"none"` | Enables no single or multiple Row selection. |
| `selected` | `Sequence[str]` | `"()"` | Supplies initially selected Row keys. |
| `name` | `str | None` | `None` | Emits selected keys as repeated hidden inputs. |
| `form` | `str | None` | `None` | Associates hidden inputs with an external form. |
| `disabled` | `bool` | `False` | Disables mutation activation and form output. |
| `density` | `CTreeGridDensity` ([`CTreeGridDensity`](#tree-grid-interface-density)) | `"comfortable"` | Selects Row height. |
| `expand_label` | `str` | `"Expand {row}"` | Overrides branch Expand names and must retain row. |
| `collapse_label` | `str` | `"Collapse {row}"` | Overrides branch Collapse names and must retain row. |
| `expanded_label` | `str` | `"Expanded {row}"` | Overrides expanded announcements and must retain row. |
| `collapsed_label` | `str` | `"Collapsed {row}"` | Overrides collapsed announcements and must retain row. |
| `selected_label` | `str` | `"Selected {row}"` | Overrides selected announcements and must retain row. |
| `unselected_label` | `str` | `"Unselected {row}"` | Overrides unselected announcements and must retain row. |
| `class_` | `CClassValue | None` ([`CClassValue`](#tree-grid-interface-class-value)) | `None` | Adds root classes. |
| `style` | `CStyleValue | None` ([`CStyleValue`](#tree-grid-interface-style-value)) | `None` | Adds root styles. |
| `attrs` | `Mapping[str, object] | None` | `None` | Adds copied allowed root attributes. |
| `table_attrs` | `Mapping[str, object] | None` | `None` | Adds copied allowed table attributes. |
#### CTreeGrid client inputs
Client inputs are passed in the browser through the `$c-props="{ ... }"` attribute on
` `.
| Input | Type | Omitted behavior | Effect |
|---|---|---|---|
| `expanded` | `string[]` | Uncontrolled server branch state. | Controls expanded branch keys. |
| `selected` | `string[]` | Uncontrolled server selection. | Controls selected Row keys. |
| `disabled` | `boolean` | Uses the server value. | Reactively disables behavior and inputs. |
| `onExpandedChange` | `function` | No component callback runs. | Receives expansion requests. |
| `onSelectionChange` | `function` | No component callback runs. | Receives selection requests. |
| `onCellActivate` | `function` | No component callback runs. | Receives Enter or double-click activation outside the hierarchy toggle. |
### Slots
Slots are passed as nested content or `` tags in a template, or through the
`slots={...}` argument in Python.
#### CTreeGrid slots
| Slot | Required | Data | Fallback |
|---|---|---|---|
| `caption` | no | `{}` ([`CTreeGridCaptionSlotData`](#tree-grid-interface-ctree-grid-caption-slot)) | No native caption. |
| `toolbar` | no | `{}` ([`CTreeGridToolbarSlotData`](#tree-grid-interface-ctree-grid-toolbar-slot)) | No toolbar. |
| `header` | no | `{column, column_index}` ([`CTreeGridHeaderSlotData`](#tree-grid-interface-ctree-grid-header-slot)) | Column label. |
| `cell` | no | `{row, column, cell, row_index, column_index, level, expanded, selected}` ([`CTreeGridCellSlotData`](#tree-grid-interface-ctree-grid-cell-slot)) | Cell value. |
### Events
Component events are callback inputs supplied through `$c-props`. Native browser events
remain available through Alpine `@...` attributes.
#### CTreeGrid events
| Event | Signature | Trigger and timing | Detail | Controlled and cancellation behavior |
|---|---|---|---|---|
| `onExpandedChange` | `(expanded: string[], detail: CTreeGridExpandedChangeDetail) => void` ([`CTreeGridExpandedChangeDetail`](#tree-grid-interface-ctree-grid-expanded-detail)) | A branch changes. | `{expanded, previousExpanded, rowKey, rowExpanded, controlled, source, sourceEvent}` ([`CTreeGridExpandedChangeDetail`](#tree-grid-interface-ctree-grid-expanded-detail)) | Commits only while uncontrolled. |
| `onSelectionChange` | `(selected: string[], detail: CTreeGridSelectionChangeDetail) => void` ([`CTreeGridSelectionChangeDetail`](#tree-grid-interface-ctree-grid-selection-detail)) | A Row selection toggles. | `{selected, previousSelected, rowKey, rowSelected, controlled, source, sourceEvent}` ([`CTreeGridSelectionChangeDetail`](#tree-grid-interface-ctree-grid-selection-detail)) | Commits only while uncontrolled. |
| `onCellActivate` | `(detail: CTreeGridCellActivateDetail) => void` ([`CTreeGridCellActivateDetail`](#tree-grid-interface-ctree-grid-activate-detail)) | Enter or double-click activates a non-hierarchy Cell. | `{rowKey, columnKey, rowIndex, columnIndex, sourceEvent}` ([`CTreeGridCellActivateDetail`](#tree-grid-interface-ctree-grid-activate-detail)) | Reports without changing data. |
### Methods
-
### CSS
CSS variables to theme the components. Set them on an ancestor or the component itself.
#### CTreeGrid CSS variables
Apply these variables to `CTreeGrid` or one of its ancestors.
| Variable | Type | Purpose | Default |
|---|---|---|---|
| `--cui-tree-grid-min-width` | `length` | Computed minimum table width. | `Sum of Column widths` |
| `--cui-tree-grid-row-height` | `length` | Comfortable Row height. | `3rem` |
| `--cui-tree-grid-indent` | `length` | Per-level logical indent. | `1.25rem` |
| `--cui-tree-grid-border` | `complete border` | Viewport Row and header boundaries. | `Adaptive 1px neutral` |
| `--cui-tree-grid-surface` | `color` | Body surface. | `Canvas` |
| `--cui-tree-grid-header-surface` | `color` | Header surface. | `Adaptive neutral` |
| `--cui-tree-grid-selected-surface` | `color` | Selected Row surface. | `Adaptive indigo` |
| `--cui-tree-grid-focus` | `color` | Gridcell and expander focus. | `Highlight` |
### Attributes
HTML attributes defined on the components that you can refer to for CSS, inspection, and
testing. Read-only.
#### CTreeGrid attributes
| Attribute | Element | Type | Meaning |
|---|---|---|---|
| `data-density` | Root | `CTreeGridDensity` ([`CTreeGridDensity`](#tree-grid-interface-density)) | Reflects Row density. |
| `data-selection` | Root | `CTreeGridSelection` ([`CTreeGridSelection`](#tree-grid-interface-selection)) | Reflects selection policy. |
| `data-disabled` | Root and Row | `present | absent` | Reflects unavailable behavior. |
| `data-expanded` | Row | `present | absent` | Reflects expanded branch state. |
| `data-selected` | Row | `present | absent` | Reflects selected state. |
| `data-row-key` | Row and Cell | `string` | Exposes stable Row identity. |
| `data-parent-key` | Row | `string | absent` | Exposes parent identity. |
| `data-level` | Row | `positive integer string` | Exposes hierarchy depth. |
### Selectors
Selectors for the DOM nodes in the components that you can use for CSS, inspection, and
testing.
#### CTreeGrid selectors
| Selector | Element | Purpose |
|---|---|---|
| `[data-citry-ui-part="tree-grid"]` | Root | Theme and state destination. |
| `[data-citry-ui-part="toolbar"]` | Optional div | Application controls. |
| `[data-citry-ui-part="status"]` | Polite status | Expansion and selection announcements. |
| `[data-citry-ui-part="viewport"]` | Scroll container | Narrow horizontal overflow. |
| `[data-citry-ui-part="table"]` | Native table with treegrid role | Composite owner. |
| `[data-citry-ui-part="header-cell"]` | Columnheader | Column label. |
| `[data-citry-ui-part="row"]` | Hierarchical Row | Expansion selection and hierarchy metadata. |
| `[data-citry-ui-part="cell"]` | Gridcell | Roving focus unit. |
| `[data-citry-ui-part="hierarchy"]` | First-Cell wrapper | Indent branch control and content. |
| `[data-citry-ui-part="expander"]` | Native button | Pointer branch toggle. |
| `[data-citry-ui-part="cell-content"]` | Span | Cell slot destination. |
| `[data-citry-ui-part="inputs"]` | Hidden span | Native selected-key controls. |
### Interfaces
Aliases and data shapes referenced above.
#### Input type aliases
| Interface | Definition |
|---|---|
| `CTreeGridSelection` | `Literal["none", "single", "multiple"]` |
| `CTreeGridDensity` | `Literal["compact", "comfortable", "spacious"]` |
| `CTreeGridAlign` | `Literal["start", "center", "end"]` |
| `CTreeGridSource` | `Literal["pointer", "keyboard", "reset"]` |
| `CClassValue` | `str | Mapping[str, bool] | Sequence[CClassValue]` |
| `CStyleValue` | `str | Mapping[str, object] | Sequence[CStyleValue]` |
#### `CTreeGridCaptionSlotData`
Empty dataclass: `{}`.
#### `CTreeGridToolbarSlotData`
Empty dataclass: `{}`.
#### `CTreeGridHeaderSlotData`
| Field | Type | Default | Meaning |
|---|---|---|---|
| `column` | `CTreeGridColumn` | - | Current Column. |
| `column_index` | `int` | - | Zero-based Column index. |
#### `CTreeGridCellSlotData`
| Field | Type | Default | Meaning |
|---|---|---|---|
| `row` | `CTreeGridRow` | - | Current Row. |
| `column` | `CTreeGridColumn` | - | Current Column. |
| `cell` | `CTreeGridCell` | - | Current Cell. |
| `row_index` | `int` | - | Zero-based flattened Row index. |
| `column_index` | `int` | - | Zero-based Column index. |
| `level` | `int` | - | One-based hierarchy depth. |
| `expanded` | `bool` | - | Initial branch expansion. |
| `selected` | `bool` | - | Initial Row selection. |
#### `CTreeGridExpandedChangeDetail`
| Field | Type | Default | Meaning |
|---|---|---|---|
| `expanded` | `list[str]` | - | Requested expanded keys. |
| `previousExpanded` | `list[str]` | - | Previous keys. |
| `rowKey` | `str` | - | Changed Row. |
| `rowExpanded` | `bool` | - | Requested Row state. |
| `controlled` | `bool` | - | Whether client state is controlled. |
| `source` | `CTreeGridSource` | - | Interaction source. |
| `sourceEvent` | `object` | - | Native Event. |
#### `CTreeGridSelectionChangeDetail`
| Field | Type | Default | Meaning |
|---|---|---|---|
| `selected` | `list[str]` | - | Requested selected keys. |
| `previousSelected` | `list[str]` | - | Previous keys. |
| `rowKey` | `str` | - | Changed Row. |
| `rowSelected` | `bool` | - | Requested Row state. |
| `controlled` | `bool` | - | Whether client state is controlled. |
| `source` | `CTreeGridSource` | - | Interaction source. |
| `sourceEvent` | `object` | - | Native Event. |
#### `CTreeGridCellActivateDetail`
| Field | Type | Default | Meaning |
|---|---|---|---|
| `rowKey` | `str` | - | Activated Row. |
| `columnKey` | `str` | - | Activated Column. |
| `rowIndex` | `int` | - | Flattened Row index. |
| `columnIndex` | `int` | - | Column index. |
| `sourceEvent` | `object` | - | Native Event. |
### Translation keys
Catalog keys used by this family. An explicit component input or slot listed in Override
takes precedence over the catalog for that instance.
#### CTreeGrid translation keys
| Key | Purpose | Variables | Override | Browser updates |
|---|---|---|---|---|
| `citry-ui-tree-grid-expand` | Names a collapsed branch control. | `row: str` | `expand_label` with `{row}` | Imperative reactive `i18n.bind()`. |
| `citry-ui-tree-grid-collapse` | Names an expanded branch control. | `row: str` | `collapse_label` with `{row}` | Imperative reactive `i18n.bind()`. |
| `citry-ui-tree-grid-expanded` | Announces branch expansion. | `row: str` | `expanded_label` with `{row}` | Browser-created one-shot `i18n.tr()`. |
| `citry-ui-tree-grid-collapsed` | Announces branch collapse. | `row: str` | `collapsed_label` with `{row}` | Browser-created one-shot `i18n.tr()`. |
| `citry-ui-tree-grid-selected` | Announces Row selection. | `row: str` | `selected_label` with `{row}` | Browser-created one-shot `i18n.tr()`. |
| `citry-ui-tree-grid-unselected` | Announces Row unselection. | `row: str` | `unselected_label` with `{row}` | Browser-created one-shot `i18n.tr()`. |
---
# Virtual List
Source: https://citry.dev/ui-library/components/virtual-list/
# Virtual List
Use `CVirtualList` when you can server-render the complete collection and want
the browser to skip off-screen layout and paint. Use `CVirtualWindow` when DOM
size is the bottleneck and your application can supply each requested
fixed-size server range. Both use `CVirtualListItem` for stable identity and
arbitrary server-rendered content.
## Keep complete server HTML
`CVirtualList` preserves every Item in the DOM and accessibility tree. It uses
`content-visibility: auto` plus an intrinsic-size estimate, so it reduces
rendering cost without reducing HTML transfer, DOM nodes, memory, Alpine roots,
or Citry initialization.
### Keep a complete virtualized list
[Open the rendered preview](/ui-library/components/virtual-list/_previews/at-a-glance/)
````citry
import citry_ui
from citry import Component, citry
citry.register_library(citry_ui)
class VirtualListAtAGlance(Component):
template = """
{{ entry['title'] }}
{{ entry['detail'] }}
"""
css = """
:where([data-citry-ui-part="virtual-list"] article) {
padding: 0.75rem 1rem;
border-block-end: 1px solid color-mix(in srgb, currentColor 14%, transparent);
}
"""
def template_data(self, _kwargs: object, _slots: object) -> dict[str, object]:
return {
"entries": [
{"key": f"build-{index}", "title": f"Build {2400 + index}", "detail": "Checks passed"}
for index in range(80)
]
}
preview = VirtualListAtAGlance()
preview # noqa: B018
````
Choose an `estimated_item_size` close to the average rendered block size. It
is a browser layout hint, not a fixed height; rich Items may still wrap and
grow. Stable `item_key` values preserve logical identity across server renders.
## Supply a true DOM window
`CVirtualWindow` renders only the contiguous range supplied by the current
server output. `total_count`, `start_index`, and `item_size` reserve the full
scroll extent. The direct `CVirtualListItem` declarations are the committed
range beginning at `start_index`.
### Supply a fixed server window
[Open the rendered preview](/ui-library/components/virtual-list/_previews/windowed/)
````citry
import citry_ui
from citry import Component, citry
citry.register_library(citry_ui)
class VirtualWindowExample(Component):
template = """
This static preview supplies one complete window
last=`Requested ${detail.startIndex}-${detail.endIndex - 1}`}"
>
{{ record['number'] }} {{ record['label'] }}
"""
css = """
:where([data-citry-ui-part="item"]) {
display: flex;
align-items: center;
gap: 0.75rem;
padding-inline: 1rem;
border-block-end: 1px solid color-mix(in srgb, currentColor 12%, transparent);
}
:where([data-citry-ui-part="item"] > span) { color: GrayText; font-variant-numeric: tabular-nums; }
"""
def template_data(self, _kwargs: object, _slots: object) -> dict[str, object]:
return {
"records": [
{"key": f"audit-{index}", "number": f"#{index + 1:05d}", "label": "Signed deployment record"}
for index in range(16)
]
}
preview = VirtualWindowExample()
preview # noqa: B018
````
Pass `onRangeChange` through `$c-props`. The callback receives the desired
overscanned half-open range, visible range, request ID, reason, and source
event. It requests state; it never mutates or renders Item HTML. Fetch or
render the new range, cancel superseded work in the application, and replace
the component with the new `start_index` and Items.
The static documentation examples use a small self-contained range with no
omitted leading or trailing Items. They therefore never expose scrollable
blank space that a static page cannot replace. A real partial range reserves
blank geometry only while the server request is pending; the owner must replace
it when `onRangeChange` fires.
The runtime marks the root `aria-busy="true"` and `data-pending` until the
committed server range covers the current desired range. A missing callback
leaves the current range usable. Callback failures are isolated and logged.
## Keep window rows fixed
Every `CVirtualWindow` Item must occupy exactly `item_size` CSS pixels in the
block axis. The component clips overflow to keep spacer geometry correct.
Use bounded internal layout, truncation, or a larger row size; do not use a
window for variable-height articles. The total scroll extent is limited to
16,000,000 CSS pixels because browser element-size limits are not portable.
### Tune range geometry
[Open the rendered preview](/ui-library/components/virtual-list/_previews/controlled/)
````citry
import citry_ui
from citry import Component, citry
citry.register_library(citry_ui)
class VirtualWindowControlled(Component):
template = """
"""
def template_data(self, _kwargs: object, _slots: object) -> dict[str, object]:
return {"indexes": list(range(12))}
css = """
:where([data-citry-ui-part="item"]) { display:flex;align-items:center;padding-inline:1rem; }
:where(label) { display:inline-flex;gap:0.5rem;margin-inline-end:1rem; }
:where(output) { display:block;margin-block:0.5rem; }
"""
preview = VirtualWindowControlled()
preview # noqa: B018
````
`overscan` and `itemSize` are reactive client inputs. A valid Alpine change
recomputes the requested range immediately. Invalid values log one diagnostic
per episode and retain the previous valid value. Use a server render when the
committed range or total count changes.
## Accessibility and focus
Both owners render `role="list"` and `CVirtualListItem` renders
`role="listitem"`. A Window Item also receives exact `aria-posinset` and
`aria-setsize`; spacers are hidden from assistive technology. `focusable=True`
adds one viewport tab stop so keyboard users can scroll even when Items contain
no controls. Use `focusable=False` only when the supplied Items contain a
keyboard-reachable control that lets keyboard users enter and scroll the
viewport. A scrollable region with neither a root tab stop nor a focusable
descendant is not keyboard accessible.
### Compare complete and windowed semantics
[Open the rendered preview](/ui-library/components/virtual-list/_previews/accessibility/)
````citry
import citry_ui
from citry import Component, citry
citry.register_library(citry_ui)
class VirtualListAccessibility(Component):
template = """
Supplied range
Release {{ index + 1 }}
"""
css = """
:where([data-citry-ui-part="item"]) {
display:flex;align-items:center;padding-inline:0.75rem;min-block-size:44px;
}
"""
def template_data(self, _kwargs: object, _slots: object) -> dict[str, object]:
return {"complete_indexes": list(range(16)), "window_indexes": list(range(8))}
preview = VirtualListAccessibility()
preview # noqa: B018
````
Use `CVirtualList` or ordinary pagination when assistive-technology users must
browse the entire collection without application range requests. Windowing
necessarily exposes only the supplied Items. Avoid windowing a long editable
form. If a focused Item or the logical owner of an open overlay leaves the
supplied range, ordinary Citry morph and owner-removal cleanup applies.
## Server rendering and JavaScript
`CVirtualList` is CSS-only and remains fully useful without JavaScript.
`CVirtualWindow` displays the supplied range at its correct offset without
JavaScript but needs JavaScript to request another range. The runtime never
clones, reparents, caches, or writes Item HTML and adds no generic client
renderer.
Server morphs are authoritative. Stable Item keys preserve the Item/component
relationship, and a retained root hands off its scroll offset across runtime
replacement. The application still owns stale-request cancellation, loading,
errors, retry, caching, and total-count changes.
## Customize the viewport and Items
Use root `class_`, `style`, and `attrs`, Item equivalents, public variables,
and documented part selectors. Window Item block size is owned geometry.
### Customize Virtual List
[Open the rendered preview](/ui-library/components/virtual-list/_previews/customization/)
````citry
import citry_ui
from citry import Component, citry
citry.register_library(citry_ui)
class VirtualListCustomization(Component):
template = """
{{ environment['name'] }}
{{ environment['region'] }}
"""
css = """
:where(.environment-list) {
--cui-virtual-list-border: 2px solid #7c3aed;
--cui-virtual-list-radius: 1rem;
--cui-virtual-list-background: light-dark(#faf5ff, #2e1065);
--cui-virtual-list-item-background: light-dark(#fff, #1e1b4b);
}
:where(.environment-list [data-citry-ui-part="item"]) {
display:grid;
grid-template-columns:1fr auto;
gap:1rem;
padding:0.875rem 1rem;
margin:0.5rem;
border-radius:0.625rem;
}
:where(.environment-list [data-citry-ui-part="item"] span) { color:GrayText; }
"""
def template_data(self, _kwargs: object, _slots: object) -> dict[str, object]:
return {
"environments": [
{"key": f"environment-{index}", "name": f"Service {index + 1}", "region": "eu-central"}
for index in range(24)
]
}
preview = VirtualListCustomization()
preview # noqa: B018
````
For print, `CVirtualList` expands and makes all Items visible. A
`CVirtualWindow` can print only its supplied range; render a separate complete
or paginated print view when the full collection matters.
## Localization
The family owns no visible or accessibility text, announcements, parsing,
formatting, filtering, sorting, or comparison. Localize `aria_label` and Item
content in the application. The family therefore has no Citry UI catalog keys.
## API reference
### Inputs
#### CVirtualList server inputs
Server inputs are passed in a template through ` ` or in Python through
`CVirtualList(...)`.
| Input | Type | Default | Effect |
|---|---|---|---|
| `aria_label` | `str | None` | `None` | Optionally names the complete-DOM list. |
| `estimated_item_size` | `int` | `48` | Sets the positive pixel intrinsic-size estimate used while off-screen Item rendering is skipped. |
| `viewport_size` | `int` | `400` | Sets the positive initial viewport block size in CSS pixels. |
| `focusable` | `bool` | `True` | Adds or removes the root tabindex=0 keyboard-scroll stop. False requires a keyboard-reachable control inside the supplied Items. |
| `class_` | `CClassValue | None` ([`CClassValue`](#virtual-list-interface-class-value)) | `None` | Adds classes to the list viewport. |
| `style` | `CStyleValue | None` ([`CStyleValue`](#virtual-list-interface-style-value)) | `None` | Adds styles before owned viewport geometry variables. |
| `attrs` | `Mapping[str, object] | None` | `None` | Adds copied allowed viewport attributes without replacing owned roles geometry state or runtime markers. |
#### CVirtualWindow server inputs
Server inputs are passed in a template through ` ` or in Python
through `CVirtualWindow(...)`.
| Input | Type | Default | Effect |
|---|---|---|---|
| `total_count` | `int` | required | Sets the exact nonnegative logical collection size. |
| `start_index` | `int` | `0` | Sets the nonnegative logical index of the first supplied Item. |
| `item_size` | `int` | `48` | Sets the positive fixed Item stride in CSS pixels; total extent cannot exceed 16000000 pixels. |
| `viewport_size` | `int` | `400` | Sets the positive initial viewport block size in CSS pixels. |
| `overscan` | `int` | `3` | Requests zero through one hundred Items before and after the visible range. |
| `initial_index` | `int` | `0` | Sets the one-shot nonnegative initial scroll index and clamps it to the collection. |
| `aria_label` | `str | None` | `None` | Optionally names the windowed list. |
| `focusable` | `bool` | `True` | Adds or removes the root tabindex=0 keyboard-scroll stop. False requires a keyboard-reachable control inside the supplied Items. |
| `class_` | `CClassValue | None` ([`CClassValue`](#virtual-list-interface-class-value)) | `None` | Adds classes to the Window viewport. |
| `style` | `CStyleValue | None` ([`CStyleValue`](#virtual-list-interface-style-value)) | `None` | Adds styles before owned viewport geometry variables. |
| `attrs` | `Mapping[str, object] | None` | `None` | Adds copied allowed viewport attributes without replacing owned roles geometry state or runtime markers. |
#### CVirtualWindow client inputs
Client inputs are passed in the browser through the `$c-props="{ ... }"` attribute on
` `.
| Input | Type | Omitted behavior | Effect |
|---|---|---|---|
| `overscan` | `int` | Uses the server value; null is invalid and retains the last valid value. | Reactively changes the requested buffer from zero through one hundred Items. |
| `itemSize` | `number` | Uses the server value; null is invalid and retains the last valid value. | Reactively changes fixed pixel geometry while the resulting total extent stays within the family limit. |
| `onRangeChange` | `function` | Omission or null selects no component callback. | Receives newest distinct range requests without committing server state. |
#### CVirtualListItem server inputs
Server inputs are passed in a template through ` ` or in Python
through `CVirtualListItem(...)`.
| Input | Type | Default | Effect |
|---|---|---|---|
| `item_key` | `str` | required | Supplies nonempty stable identity unique within the owning logical collection or supplied range. |
| `class_` | `CClassValue | None` ([`CClassValue`](#virtual-list-interface-class-value)) | `None` | Adds classes to the rendered list Item. |
| `style` | `CStyleValue | None` ([`CStyleValue`](#virtual-list-interface-style-value)) | `None` | Adds styles to the Item; Window fixed block size remains owned. |
| `attrs` | `Mapping[str, object] | None` | `None` | Adds copied allowed Item attributes without replacing owned roles positions identity or runtime markers. |
### Slots
Slots are passed as nested content or `` tags in a template, or through the
`slots={...}` argument in Python.
#### CVirtualList slots
| Slot | Required | Data | Fallback |
|---|---|---|---|
| `default` | no | `{}` ([`CVirtualListDefaultSlotData`](#virtual-list-interface-cvirtual-list-default-slot-data)) | Empty list; accepts only CVirtualListItem declarations. |
#### CVirtualWindow slots
| Slot | Required | Data | Fallback |
|---|---|---|---|
| `default` | no | `{}` ([`CVirtualListDefaultSlotData`](#virtual-list-interface-cvirtual-list-default-slot-data)) | Empty supplied range; accepts only CVirtualListItem declarations. |
#### CVirtualListItem slots
| Slot | Required | Data | Fallback |
|---|---|---|---|
| `default` | yes | `{index, item_key, set_size, strategy}` ([`CVirtualListItemDefaultSlotData`](#virtual-list-interface-cvirtual-list-item-default-slot-data)) | None. |
### Events
Component events are callback inputs supplied through `$c-props`. Native browser events
remain available through Alpine `@...` attributes.
#### CVirtualWindow events
| Event | Signature | Trigger and timing | Detail | Controlled and cancellation behavior |
|---|---|---|---|---|
| `onRangeChange` | `(detail: CVirtualListRangeChangeDetail) => void` ([`CVirtualListRangeChangeDetail`](#virtual-list-interface-cvirtual-list-range-change-detail)) | The visible overscanned range is not covered by the committed server range. | `{startIndex, endIndex, visibleStartIndex, visibleEndIndex, requestId, reason, sourceEvent}` ([`CVirtualListRangeChangeDetail`](#virtual-list-interface-cvirtual-list-range-change-detail)) | Animation-frame-coalesced request only. The application supplies a new server range and owns cancellation supersession loading error and retry. |
### Methods
-
### CSS
CSS variables to theme the components. Set them on an ancestor or the component itself.
#### CVirtualList CSS variables
Apply these variables to `CVirtualList` or one of its ancestors.
| Variable | Type | Purpose | Default |
|---|---|---|---|
| `--cui-virtual-list-viewport-size` | `length` | Viewport block size read by both root owners. | `Server viewport_size; 400px` |
| `--cui-virtual-list-item-size` | `length` | Complete-DOM intrinsic estimate or owned Window Item stride. | `Server estimate or item_size; 48px` |
| `--cui-virtual-list-border` | `complete border value` | Viewport border. | `Adaptive 1px solid neutral` |
| `--cui-virtual-list-radius` | `length` | Viewport corner radius. | `0.625rem` |
| `--cui-virtual-list-background` | `color` | Viewport background. | `Canvas` |
| `--cui-virtual-list-item-background` | `color` | Item background. | `transparent` |
### Attributes
HTML attributes defined on the components that you can refer to for CSS, inspection, and
testing. Read-only.
#### CVirtualList attributes
| Attribute | Element | Type | Meaning |
|---|---|---|---|
| `role` | Root viewport div | `list` | Exposes noninteractive list semantics. |
| `aria-label` | Root viewport div | `string | absent` | Optional application-localized list name. |
| `tabindex` | Root viewport div | `0 | absent` | Adds keyboard-scroll focus when focusable is true. |
| `data-strategy` | Root viewport div | `content-visibility` | Identifies complete-DOM containment behavior. |
#### CVirtualWindow attributes
| Attribute | Element | Type | Meaning |
|---|---|---|---|
| `role` | Root viewport div | `list` | Exposes noninteractive list semantics. |
| `aria-label` | Root viewport div | `string | absent` | Optional application-localized list name. |
| `aria-busy` | Root viewport div | `true | absent` | Present while the current desired range is not covered. |
| `tabindex` | Root viewport div | `0 | absent` | Adds keyboard-scroll focus when focusable is true. |
| `data-strategy` | Root viewport div | `window` | Identifies true controlled window behavior. |
| `data-pending` | Root viewport div | `present | absent` | Mirrors an uncovered desired range. |
| `data-start-index` | Root viewport div | `nonnegative-integer-string` | Mirrors the committed server range start. |
| `data-total-count` | Root viewport div | `nonnegative-integer-string` | Mirrors logical collection size. |
#### CVirtualListItem attributes
| Attribute | Element | Type | Meaning |
|---|---|---|---|
| `role` | Item div | `listitem` | Exposes one noninteractive list item. |
| `data-index` | Item div | `nonnegative-integer-string` | Exposes settled logical zero-based position. |
| `data-item-key` | Item div | `string` | Exposes stable server identity. |
| `aria-posinset` | Window Item div | `positive-integer-string | absent` | Exposes one-based logical position only in CVirtualWindow. |
| `aria-setsize` | Window Item div | `nonnegative-integer-string | absent` | Exposes total logical size only in CVirtualWindow. |
### Selectors
Selectors for the DOM nodes in the components that you can use for CSS, inspection, and
testing.
#### CVirtualList selectors
| Selector | Element | Purpose |
|---|---|---|
| `[data-citry-ui-part="virtual-list"]` | Root viewport div | Root attrs and viewport customization destination for both owners. |
| `[data-citry-ui-part="track"]` | Direct track div | Contains spacers and supplied Items. |
| `[data-citry-ui-part="item"]` | Item div | Stable Item attrs content and customization destination. |
| `[data-citry-ui-part="spacer"]` | Window-only aria-hidden div | Reserves omitted range space; geometry is owned. |
### Interfaces
Aliases and data shapes referenced above.
#### Input type aliases
| Interface | Definition |
|---|---|
| `CVirtualListStrategy` | `Literal["content-visibility", "window"]` |
| `CVirtualListRangeReason` | `Literal["initial", "scroll", "resize", "configuration"]` |
| `CClassValue` | `str | Mapping[str, bool] | Sequence[CClassValue]` |
| `CStyleValue` | `str | Mapping[str, object] | Sequence[CStyleValue]` |
#### `CVirtualListDefaultSlotData`
Empty dataclass: `{}`.
#### `CVirtualListItemDefaultSlotData`
| Field | Type | Default | Meaning |
|---|---|---|---|
| `index` | `int` | - | Settled logical zero-based Item position. |
| `item_key` | `str` | - | Stable authored Item identity. |
| `set_size` | `int` | - | Complete declaration count or Window total_count. |
| `strategy` | `CVirtualListStrategy` ([`CVirtualListStrategy`](#virtual-list-interface-strategy)) | - | Identifies the owning complete-DOM or Window behavior. |
#### `CVirtualListRangeChangeDetail`
| Field | Type | Default | Meaning |
|---|---|---|---|
| `startIndex` | `int` | - | Inclusive requested overscanned range start. |
| `endIndex` | `int` | - | Exclusive requested overscanned range end. |
| `visibleStartIndex` | `int` | - | Inclusive geometrically visible range start. |
| `visibleEndIndex` | `int` | - | Exclusive geometrically visible range end. |
| `requestId` | `int` | - | Monotonically increasing instance-local request identifier. |
| `reason` | `CVirtualListRangeReason` ([`CVirtualListRangeReason`](#virtual-list-interface-range-reason)) | - | Geometry trigger that scheduled the latest request frame. |
| `sourceEvent` | `Event | null` | - | Latest native scroll event when reason is scroll; otherwise null. |
### Translation keys
-
---
# Breadcrumbs
Source: https://citry.dev/ui-library/components/breadcrumbs/
# Breadcrumbs
Use `CBreadcrumbs` to show the current page within a hierarchy and link back to
its ancestors.
## Breadcrumbs at a glance
### Breadcrumbs at a glance
[Open the rendered preview](/ui-library/components/breadcrumbs/_previews/at-a-glance/)
````citry
import citry_ui
from citry import Component, citry
citry.register_library(citry_ui)
class BreadcrumbsAtAGlance(Component):
class Kwargs:
pass
class Slots:
pass
def template_data(self, kwargs: Kwargs, slots: Slots) -> dict[str, object]: # noqa: ARG002
return {
"items": (
citry_ui.CBreadcrumbItem("Library", "/library"),
citry_ui.CBreadcrumbItem("Natural history", "/library/nature"),
citry_ui.CBreadcrumbItem("The hidden life of trees"),
)
}
template = """
The hidden life of trees
Essays on forests, roots, and the communities beneath them.
"""
css = """
:where(.breadcrumb-shelf) {
display: grid;
gap: 0.75rem;
max-inline-size: 42rem;
padding: 1.25rem;
border: 1px solid light-dark(#b8aa92, #655a49);
border-radius: 0.9rem;
color: CanvasText;
font-family: ui-serif, Georgia, serif;
}
:where(.breadcrumb-shelf h2, .breadcrumb-shelf p) {
margin: 0;
}
"""
preview = BreadcrumbsAtAGlance()
preview # noqa: B018
````
## Build a trail from records
The final item is current. Give earlier items an `href`; leave the final href
empty for plain current-page text.
### Build a Breadcrumb trail
[Open the rendered preview](/ui-library/components/breadcrumbs/_previews/basic/)
````citry
import citry_ui
from citry import Component, citry
citry.register_library(citry_ui)
class BasicBreadcrumbs(Component):
class Kwargs:
pass
class Slots:
pass
def template_data(self, kwargs: Kwargs, slots: Slots) -> dict[str, object]: # noqa: ARG002
return {
"items": (
citry_ui.CBreadcrumbItem("Library", "/library"),
citry_ui.CBreadcrumbItem("Fiction", "/library/fiction"),
citry_ui.CBreadcrumbItem("The left hand of darkness"),
)
}
template = ' '
preview = BasicBreadcrumbs()
preview # noqa: B018
````
```py
items = (
CBreadcrumbItem("Home", "/"),
CBreadcrumbItem("Library", "/library"),
CBreadcrumbItem("The green room"),
)
```
## Keep the current page linked
A final item may retain its href. Citry adds `aria-current="page"`.
### Link the current page
[Open the rendered preview](/ui-library/components/breadcrumbs/_previews/current-link/)
````citry
import citry_ui
from citry import Component, citry
citry.register_library(citry_ui)
class LinkedCurrentBreadcrumb(Component):
class Kwargs:
pass
class Slots:
pass
def template_data(self, kwargs: Kwargs, slots: Slots) -> dict[str, object]: # noqa: ARG002
return {
"items": (
citry_ui.CBreadcrumbItem("Library", "/library"),
citry_ui.CBreadcrumbItem("New arrivals", "/library/new"),
)
}
template = ' '
preview = LinkedCurrentBreadcrumb()
preview # noqa: B018
````
## Choose a separator
Use concise text directly or replace each separator through the scoped slot.
Separators stay hidden from assistive technology.
### Choose Breadcrumb separators
[Open the rendered preview](/ui-library/components/breadcrumbs/_previews/separators/)
````citry
import citry_ui
from citry import Component, citry
citry.register_library(citry_ui)
class BreadcrumbSeparators(Component):
class Kwargs:
pass
class Slots:
pass
def template_data(self, kwargs: Kwargs, slots: Slots) -> dict[str, object]: # noqa: ARG002
return {
"items": (
citry_ui.CBreadcrumbItem("Poetry", "/poetry"),
citry_ui.CBreadcrumbItem("Mary Oliver"),
)
}
template = """
→
"""
preview = BreadcrumbSeparators()
preview # noqa: B018
````
## Choose size
Use `sm`, `md`, or `lg` to match the surrounding navigation density.
### Compare Breadcrumb sizes
[Open the rendered preview](/ui-library/components/breadcrumbs/_previews/sizes/)
````citry
import citry_ui
from citry import Component, citry
citry.register_library(citry_ui)
class BreadcrumbSizes(Component):
class Kwargs:
pass
class Slots:
pass
def template_data(self, kwargs: Kwargs, slots: Slots) -> dict[str, object]: # noqa: ARG002
return {
"items": (
citry_ui.CBreadcrumbItem("Essays", "/essays"),
citry_ui.CBreadcrumbItem("On keeping a notebook"),
),
"sizes": ("sm", "md", "lg"),
}
template = """
"""
preview = BreadcrumbSizes()
preview # noqa: B018
````
## Wrap or scroll long trails
Wrapping is the default. Set `wrap=False` for one horizontal scroll row.
### Handle long Breadcrumb trails
[Open the rendered preview](/ui-library/components/breadcrumbs/_previews/overflow/)
````citry
import citry_ui
from citry import Component, citry
citry.register_library(citry_ui)
class BreadcrumbOverflow(Component):
class Kwargs:
pass
class Slots:
pass
def template_data(self, kwargs: Kwargs, slots: Slots) -> dict[str, object]: # noqa: ARG002
labels = ("Library", "Collections", "Natural history", "Forests", "Temperate woodland", "Field notes")
return {
"items": tuple(
citry_ui.CBreadcrumbItem(label, f"/shelf/{index}")
if index < len(labels) - 1
else citry_ui.CBreadcrumbItem(label)
for index, label in enumerate(labels)
)
}
template = """
"""
css = """
:where(.breadcrumb-overflow) {
inline-size: min(100%, 22rem);
padding: 1rem;
border: 1px solid color-mix(in srgb, CanvasText 24%, transparent);
}
"""
preview = BreadcrumbOverflow()
preview # noqa: B018
````
## Customize item rendering
The `item` slot receives the record, index, current flag, and owned native attrs.
Bind `attrs` to preserve link and current-page semantics.
### Customize Breadcrumb items
[Open the rendered preview](/ui-library/components/breadcrumbs/_previews/item-slot/)
````citry
import citry_ui
from citry import Component, citry
citry.register_library(citry_ui)
class BreadcrumbItemSlot(Component):
class Kwargs:
pass
class Slots:
pass
def template_data(self, kwargs: Kwargs, slots: Slots) -> dict[str, object]: # noqa: ARG002
return {
"items": (
citry_ui.CBreadcrumbItem("Reading lists", "/lists"),
citry_ui.CBreadcrumbItem("Summer shelf"),
)
}
template = """
◌
{{ item.label }}
{{ item.label }}
"""
preview = BreadcrumbItemSlot()
preview # noqa: B018
````
## Compose route-derived records
Route integration stays outside the component. Turn your router hierarchy into
`CBreadcrumbItem` records and pass the resulting tuple.
### Compose route-derived Breadcrumbs
[Open the rendered preview](/ui-library/components/breadcrumbs/_previews/route-records/)
````citry
import citry_ui
from citry import Component, citry
citry.register_library(citry_ui)
class RouteBreadcrumbs(Component):
class Kwargs:
pass
class Slots:
pass
def template_data(self, kwargs: Kwargs, slots: Slots) -> dict[str, object]: # noqa: ARG002
route = (("Authors", "/authors"), ("Ursula K. Le Guin", "/authors/le-guin"), ("Books", None))
return {"items": tuple(citry_ui.CBreadcrumbItem(label, href) for label, href in route)}
template = ' '
preview = RouteBreadcrumbs()
preview # noqa: B018
````
## Customize Breadcrumbs
Override public link, current, separator, focus, and spacing variables or stable
parts.
### Customize Breadcrumbs with public CSS
[Open the rendered preview](/ui-library/components/breadcrumbs/_previews/customization/)
````citry
import citry_ui
from citry import Component, citry
citry.register_library(citry_ui)
class CustomBreadcrumbs(Component):
class Kwargs:
pass
class Slots:
pass
def template_data(self, kwargs: Kwargs, slots: Slots) -> dict[str, object]: # noqa: ARG002
return {
"items": (
citry_ui.CBreadcrumbItem("Archive", "/archive"),
citry_ui.CBreadcrumbItem("Rare books"),
)
}
template = ' '
css = """
:where(.rare-trail) {
--cui-breadcrumbs-link-color: light-dark(#7c2d12, #fdba74);
--cui-breadcrumbs-current-color: light-dark(#4c1d95, #c4b5fd);
--cui-breadcrumbs-separator-color: light-dark(#9a3412, #fb923c);
--cui-breadcrumbs-gap: 0.8rem;
padding: 1rem;
border-block: 1px solid color-mix(in srgb, CanvasText 24%, transparent);
}
"""
preview = CustomBreadcrumbs()
preview # noqa: B018
````
## API reference
### Inputs
#### CBreadcrumbs server inputs
Server inputs are passed in a template through ` ` or in Python through
`CBreadcrumbs(...)`.
| Input | Type | Default | Effect |
|---|---|---|---|
| `items` | `Sequence[CBreadcrumbItem]` | required | Renders a nonempty hierarchy whose final record is current. |
| `label` | `str` | `"Breadcrumbs"` | Names the navigation landmark. |
| `separator` | `str` | `"/"` | Sets hidden-from-AT visual separator fallback. |
| `size` | `"sm" | "md" | "lg"` ([`CBreadcrumbsSize`](#breadcrumbs-interface-size)) | `"md"` | Sets trail type scale. |
| `wrap` | `bool` | `True` | Wraps the trail; false keeps one horizontally scrollable row. |
| `class_` | `str | Mapping[str, bool] | Sequence[CClassValue] | None` ([`CClassValue`](#breadcrumbs-interface-class-value)) | `None` | Adds root classes and merges them with attrs. |
| `style` | `str | Mapping[str, str | int | float | bool | None] | Sequence[CStyleValue] | None` ([`CStyleValue`](#breadcrumbs-interface-style-value)) | `None` | Adds root inline styles and merges them with attrs. |
| `attrs` | `Mapping[str, object] | None` | `None` | Adds copied trusted nonconflicting nav metadata. |
| `list_attrs` | `Mapping[str, object] | None` | `None` | Adds copied trusted nonconflicting ordered-list metadata. |
### Slots
Slots are passed as nested content or `` tags in a template, or through the
`slots={...}` argument in Python.
#### CBreadcrumbs slots
| Slot | Required | Data | Fallback |
|---|---|---|---|
| `item` | no | `{item: CBreadcrumbItem, index: int, is_current: bool, attrs: Mapping[str, object]}` ([`CBreadcrumbsItemSlotData`](#breadcrumbs-interface-item-slot-data)) | Renders the record as a native anchor or current span. |
| `separator` | no | `{index: int}` ([`CBreadcrumbsSeparatorSlotData`](#breadcrumbs-interface-separator-slot-data)) | Renders the `separator` input inside its hidden wrapper. |
### Events
-
### Methods
-
### CSS
CSS variables to theme the components. Set them on an ancestor or the component itself.
#### CBreadcrumbs CSS variables
Apply these variables to `CBreadcrumbs` or one of its ancestors.
| Variable | Type | Purpose | Default |
|---|---|---|---|
| `--cui-breadcrumbs-foreground` | `color` | Root inherited foreground. | `CanvasText.` |
| `--cui-breadcrumbs-link-color` | `color` | Ancestor link color. | `LinkText.` |
| `--cui-breadcrumbs-current-color` | `color` | Current-page color. | `CanvasText.` |
| `--cui-breadcrumbs-separator-color` | `color` | Visual separator color. | `Scheme-aware muted foreground.` |
| `--cui-breadcrumbs-gap` | `length` | Item and separator spacing. | `0.5rem.` |
| `--cui-breadcrumbs-focus-color` | `color` | Link keyboard focus ring. | `Highlight.` |
### Attributes
HTML attributes defined on the components that you can refer to for CSS, inspection, and
testing. Read-only.
#### CBreadcrumbs attributes
| Attribute | Element | Type | Meaning |
|---|---|---|---|
| `aria-label` | Nav root | `nonempty string` | Names the navigation landmark. |
| `href` | Linked item anchor | `nonempty string` | Native ancestor or linked-current destination. |
| `aria-current` | Final anchor or span | `"page"` | Marks the final item as current. |
| `data-size` | Nav root | `"sm" | "md" | "lg"` | Mirrors type scale. |
| `data-wrap` | Nav root | `boolean present or absent` | Present while the trail wraps. |
### Selectors
Selectors for the DOM nodes in the components that you can use for CSS, inspection, and
testing.
#### CBreadcrumbs selectors
| Selector | Element | Purpose |
|---|---|---|
| `[data-citry-ui-part="breadcrumbs"]` | Nav root | Landmark and root attrs destination. |
| `[data-citry-ui-part="list"]` | Ordered list | Trail layout and list attrs destination. |
| `[data-citry-ui-part="item"]` | List item | One hierarchy record. |
| `[data-citry-ui-part="link"]` | Native anchor | Navigable ancestor or linked current item. |
| `[data-citry-ui-part="current"]` | Span | Plain current-page item. |
| `[data-citry-ui-part="separator"]` | Hidden-from-AT span | Visual hierarchy separator. |
### Interfaces
Aliases and data shapes referenced above.
#### Input type aliases
| Interface | Definition |
|---|---|
| `CClassValue` | `str | Mapping[str, bool] | Sequence[CClassValue]` |
| `CStyleValue` | `str | Mapping[str, str | int | float | bool | None] | Sequence[CStyleValue]` |
| `CBreadcrumbsSize` | `Literal["sm", "md", "lg"]` |
#### `CBreadcrumbItem`
| Field | Type | Default | Meaning |
|---|---|---|---|
| `label` | `str` | - | Visible nonempty item text. |
| `href` | `str | None` | - | Native destination; None renders plain text. |
| `attrs` | `Mapping[str, object] | None` | - | Copied trusted nonconflicting anchor/span attrs. |
#### `CBreadcrumbsItemSlotData`
| Field | Type | Default | Meaning |
|---|---|---|---|
| `item` | `CBreadcrumbItem` | - | Normalized item record. |
| `index` | `int` | - | Zero-based hierarchy position. |
| `is_current` | `bool` | - | True only for the final item. |
| `attrs` | `Mapping[str, object]` | - | Required native href/current attrs plus record attrs. |
#### `CBreadcrumbsSeparatorSlotData`
| Field | Type | Default | Meaning |
|---|---|---|---|
| `index` | `int` | - | Zero-based preceding item index. |
### Translation keys
Catalog keys used by this family. An explicit component input or slot listed in Override
takes precedence over the catalog for that instance.
#### CBreadcrumbs translation keys
| Key | Purpose | Variables | Override | Browser updates |
|---|---|---|---|---|
| `citry-ui-breadcrumbs-label` | Names the navigation landmark. | `None` | `label` input | $c-tr updates `aria-label`. |
---
# NavigationMenu
Source: https://citry.dev/ui-library/components/navigation-menu/
# NavigationMenu
Use `CNavigationMenu` for persistent site navigation whose top-level entries
are native links or Buttons that disclose richer link collections. It keeps
ordinary `nav`, list, link, and Tab behavior—application commands belong in
`CMenu`.
## NavigationMenu at a glance
### NavigationMenu at a glance
[Open the rendered preview](/ui-library/components/navigation-menu/_previews/at-a-glance/)
````citry
# ruff: noqa: E501
import citry_ui
from citry import Component, citry
citry.register_library(citry_ui)
class NavigationMenuAtAGlance(Component):
template = """
Overview
Products
Explore products Analytics Automations
Pricing
"""
preview = NavigationMenuAtAGlance()
preview # noqa: B018
````
## Link-only navigation
### Native navigation links
[Open the rendered preview](/ui-library/components/navigation-menu/_previews/links/)
````citry
import citry_ui
from citry import Component, citry
citry.register_library(citry_ui)
class NavigationLinks(Component):
template = """
Guide
Reference
Examples
"""
preview = NavigationLinks()
preview # noqa: B018
````
## Rich navigation panels
### Rich navigation panels
[Open the rendered preview](/ui-library/components/navigation-menu/_previews/panels/)
````citry
# ruff: noqa: E501
import citry_ui
from citry import Component, citry
citry.register_library(citry_ui)
class RichNavigationPanels(Component):
template = """
Home
Platform
Observe Capture field signals. Coordinate Keep teams aligned.
Company
"""
preview = RichNavigationPanels()
preview # noqa: B018
````
## Control the open panel
### Controlled NavigationMenu
[Open the rendered preview](/ui-library/components/navigation-menu/_previews/controlled/)
````citry
# ruff: noqa: E501
import citry_ui
from citry import Component, citry
citry.register_library(citry_ui)
class ControlledNavigation(Component):
template = """
"""
preview = ControlledNavigation()
preview # noqa: B018
````
## Choose orientation
### NavigationMenu orientations
[Open the rendered preview](/ui-library/components/navigation-menu/_previews/orientation/)
````citry
# ruff: noqa: E501
import citry_ui
from citry import Component, citry
citry.register_library(citry_ui)
class NavigationOrientation(Component):
template = """
Profile
Teams Research Operations
Billing
"""
preview = NavigationOrientation()
preview # noqa: B018
````
## Disabled states
### NavigationMenu states
[Open the rendered preview](/ui-library/components/navigation-menu/_previews/states/)
````citry
# ruff: noqa: E501
import citry_ui
from citry import Component, citry
citry.register_library(citry_ui)
class NavigationStates(Component):
template = """
Current page
Available Ready to explore.
Unavailable Hidden panel.
"""
preview = NavigationStates()
preview # noqa: B018
````
## Variants and sizes
### NavigationMenu variants and sizes
[Open the rendered preview](/ui-library/components/navigation-menu/_previews/variants/)
````citry
# ruff: noqa: E501
import citry_ui
from citry import Component, citry
citry.register_library(citry_ui)
class NavigationVariants(Component):
template = """
Small More Small panel Large Details Large panel
"""
preview = NavigationVariants()
preview # noqa: B018
````
## Keyboard navigation
### NavigationMenu keyboard behavior
[Open the rendered preview](/ui-library/components/navigation-menu/_previews/keyboard/)
````citry
# ruff: noqa: E501
import citry_ui
from citry import Component, citry
citry.register_library(citry_ui)
class NavigationKeyboard(Component):
template = """
Tab normally. Use Arrow keys between top-level controls, Down to enter an open panel, and Escape to close it.
Start Topics Accessibility Finish
"""
preview = NavigationKeyboard()
preview # noqa: B018
````
## Customize NavigationMenu
### Customize NavigationMenu
[Open the rendered preview](/ui-library/components/navigation-menu/_previews/customization/)
````citry
# ruff: noqa: E501
import citry_ui
from citry import Component, citry
citry.register_library(citry_ui)
class CustomNavigation(Component):
template = """
Mission Field notes Fresh observations Follow the latest work from the field.
"""
css = """
.aurora-nav { --cui-navigation-menu-trigger-open-background:#dbeafe; --cui-navigation-menu-radius:1rem; --cui-navigation-menu-panel-inline-size:20rem; }
"""
preview = CustomNavigation()
preview # noqa: B018
````
## Accessibility and interaction
Give every root a concise `label`. Links remain native and all top-level links
and disclosure Buttons remain in ordinary Tab order. Arrow keys provide an
additional convenience between top-level controls; Escape closes an open panel
and returns focus to its Button. Panels can contain ordinary links, Buttons,
and forms, but nested NavigationMenu disclosures are intentionally deferred.
## API reference
### Inputs
#### CNavigationMenu server inputs
Server inputs are passed in a template through ` ` or in Python
through `CNavigationMenu(...)`.
| Input | Type | Default | Effect |
|---|---|---|---|
| `label` | `str` | required | Names the native navigation landmark. |
| `id` | `str | None` | generated | Sets root identity and generated trigger/panel relationship prefixes. |
| `value` | `str | None` | `None` | Selects the server-open Item and uncontrolled fallback. |
| `orientation` | `"horizontal" | "vertical"` ([`CNavigationMenuOrientation`](#navigation-menu-interface-orientation)) | `"horizontal"` | Sets visual layout and optional arrow-key axis. |
| `disabled` | `bool` | `False` | Prevents disclosure opening and forces an open panel closed. |
| `delay` | `int` | `200` | Sets fine-pointer open delay from 0 through 60000 milliseconds. |
| `close_delay` | `int` | `300` | Sets fine-pointer root-leave close delay from 0 through 60000 milliseconds. |
| `loop` | `bool` | `False` | Allows optional top-level arrow navigation to wrap. |
| `variant` | `"plain" | "surface"` ([`CNavigationMenuVariant`](#navigation-menu-interface-variant)) | `"plain"` | Selects root visual treatment. |
| `size` | `"sm" | "md" | "lg"` ([`CNavigationMenuSize`](#navigation-menu-interface-size)) | `"md"` | Selects geometry for the complete tree. |
| `class_` | `CClassValue` ([`CClassValue`](#navigation-menu-interface-class-value)) | `None` | Adds root classes. |
| `style` | `CStyleValue` ([`CStyleValue`](#navigation-menu-interface-style-value)) | `None` | Adds root inline styles. |
| `attrs` | `Mapping[str, object] | None` | `None` | Adds allowed native and data attributes to the nav root. |
#### CNavigationMenu client inputs
Client inputs are passed in the browser through the `$c-props="{ ... }"` attribute on
` `.
| Input | Type | Omitted behavior | Effect |
|---|---|---|---|
| `value` | `string | null` | Releases control and preserves committed state. | Controls the open Item while supplied. |
| `disabled` | `boolean` | Uses the server input. | Reactively disables disclosure behavior. |
| `delay` | `integer` | Uses the server input. | Controls future pointer-open delay. |
| `closeDelay` | `integer` | Uses the server input. | Controls future pointer-close delay. |
| `loop` | `boolean` | Uses the server input. | Controls arrow-key wrapping. |
| `orientation` | `CNavigationMenuOrientation` | Uses the server input. | Changes layout and keyboard axis. |
| `variant` | `CNavigationMenuVariant` | Uses the server input. | Changes root treatment. |
| `size` | `CNavigationMenuSize` | Uses the server input. | Changes tree geometry. |
| `onValueChange` | `function` | Does not notify a component callback. | Receives open-value requests and forced safety closes. |
#### CNavigationMenuLink server inputs
Server inputs are passed in a template through ` ` or in Python
through `CNavigationMenuLink(...)`.
| Input | Type | Default | Effect |
|---|---|---|---|
| `href` | `str` | required | Sets the native link destination without URL rewriting. |
| `current` | `bool` | `False` | Emits aria-current page. |
| `target` | `str | None` | `None` | Sets native target. |
| `rel` | `str | None` | `None` | Sets native rel. |
| `download` | `str | None` | `None` | Sets native download. |
| `class_` | `CClassValue` ([`CClassValue`](#navigation-menu-interface-class-value)) | `None` | Adds list-item classes. |
| `style` | `CStyleValue` ([`CStyleValue`](#navigation-menu-interface-style-value)) | `None` | Adds list-item styles. |
| `attrs` | `Mapping[str, object] | None` | `None` | Adds allowed list-item attributes. |
| `link_attrs` | `Mapping[str, object] | None` | `None` | Adds allowed native link attributes. |
#### CNavigationMenuItem server inputs
Server inputs are passed in a template through ` ` or in Python
through `CNavigationMenuItem(...)`.
| Input | Type | Default | Effect |
|---|---|---|---|
| `value` | `str` | required | Sets unique Item identity and callback value. |
| `disabled` | `bool` | `False` | Disables the disclosure Button. |
| `class_` | `CClassValue` ([`CClassValue`](#navigation-menu-interface-class-value)) | `None` | Adds list-item classes. |
| `style` | `CStyleValue` ([`CStyleValue`](#navigation-menu-interface-style-value)) | `None` | Adds list-item styles. |
| `attrs` | `Mapping[str, object] | None` | `None` | Adds allowed list-item attributes. |
| `trigger_attrs` | `Mapping[str, object] | None` | `None` | Adds allowed native Button attributes. |
| `panel_attrs` | `Mapping[str, object] | None` | `None` | Adds allowed panel attributes. |
### Slots
Slots are passed as nested content or `` tags in a template, or through the
`slots={...}` argument in Python.
#### CNavigationMenu slots
| Slot | Required | Data | Fallback |
|---|---|---|---|
| `default` | yes | `{}` ([`CNavigationMenuDefaultSlotData`](#navigation-menu-interface-root-slot)) | none |
#### CNavigationMenuLink slots
| Slot | Required | Data | Fallback |
|---|---|---|---|
| `default` | yes | `{}` ([`CNavigationMenuLinkDefaultSlotData`](#navigation-menu-interface-link-slot)) | none |
#### CNavigationMenuItem slots
| Slot | Required | Data | Fallback |
|---|---|---|---|
| `label` | yes | `{}` ([`CNavigationMenuItemLabelSlotData`](#navigation-menu-interface-item-label-slot)) | none |
| `default` | yes | `{}` ([`CNavigationMenuItemDefaultSlotData`](#navigation-menu-interface-item-default-slot)) | none |
### Events
Component events are callback inputs supplied through `$c-props`. Native browser events
remain available through Alpine `@...` attributes.
#### CNavigationMenu events
| Event | Signature | Trigger and timing | Detail | Controlled and cancellation behavior |
|---|---|---|---|---|
| `onValueChange` | `(value: string | null, detail: CNavigationMenuValueChangeDetail) => void` ([`CNavigationMenuValueChangeDetail`](#navigation-menu-interface-value-change-detail)) | Trigger, hover, Escape, outside interaction, link activation, disabledness, or structure requests a different open value. | `{value, previousValue, reason, controlled, forced, source}` ([`CNavigationMenuValueChangeDetail`](#navigation-menu-interface-value-change-detail)) | Uncontrolled requests commit before notification; controlled requests wait for acceptance; safety closes are forced. |
### Methods
-
### CSS
CSS variables to theme the components. Set them on an ancestor or the component itself.
#### CNavigationMenu CSS variables
Apply these variables to `CNavigationMenu` or one of its ancestors.
| Variable | Type | Purpose | Default |
|---|---|---|---|
| `--cui-navigation-menu-background` | `color` | Root background. | `transparent` |
| `--cui-navigation-menu-foreground` | `color` | Tree foreground. | `CanvasText` |
| `--cui-navigation-menu-border-color` | `color` | Surface boundaries. | `Scheme-aware neutral.` |
| `--cui-navigation-menu-trigger-background` | `color` | Resting top-level control background. | `transparent` |
| `--cui-navigation-menu-trigger-hover-background` | `color` | Hovered control background. | `Scheme-aware neutral.` |
| `--cui-navigation-menu-trigger-open-background` | `color` | Open control background. | `Scheme-aware neutral.` |
| `--cui-navigation-menu-focus-color` | `color` | Focus ring. | `Highlight` |
| `--cui-navigation-menu-radius` | `length` | Root and panel radius. | `0.75rem` |
| `--cui-navigation-menu-gap` | `length` | Top-level gap. | `0.25rem` |
| `--cui-navigation-menu-padding` | `length` | Root padding. | `Size-derived.` |
| `--cui-navigation-menu-panel-background` | `color` | Panel surface. | `Canvas` |
| `--cui-navigation-menu-panel-inline-size` | `length` | Preferred panel width. | `24rem` |
| `--cui-navigation-menu-panel-max-inline-size` | `length` | Viewport-safe maximum panel width. | `calc(100vw - 2rem)` |
| `--cui-navigation-menu-panel-padding` | `length` | Panel padding. | `1rem` |
| `--cui-navigation-menu-panel-shadow` | `shadow` | Panel elevation. | `Scheme-aware shadow.` |
| `--cui-navigation-menu-offset` | `length` | Panel offset. | `0.45rem` |
| `--cui-navigation-menu-duration` | `time` | Indicator transition duration. | `150ms` |
| `--cui-navigation-menu-easing` | `easing` | Indicator transition easing. | `ease-out` |
### Attributes
HTML attributes defined on the components that you can refer to for CSS, inspection, and
testing. Read-only.
#### CNavigationMenu attributes
| Attribute | Element | Type | Meaning |
|---|---|---|---|
| `aria-label` | Root nav | `string` | Names the navigation landmark. |
| `data-orientation` | Root nav | `CNavigationMenuOrientation` | Reflects layout and arrow-key axis. |
| `data-disabled` | Root and disabled Item/trigger | `present | absent` | Reflects effective component disabledness. |
| `data-loop` | Root nav | `present | absent` | Reflects arrow wrapping. |
| `data-variant` | Root nav | `CNavigationMenuVariant` | Reflects treatment. |
| `data-size` | Root nav | `CNavigationMenuSize` | Reflects geometry. |
| `data-value` | Root Item trigger and panel | `string` | Reflects open or owned Item identity according to destination. |
| `data-open` | Open Item trigger and panel | `present | absent` | Reflects open state. |
| `aria-current` | Current Link | `"page"` | Identifies the current destination. |
| `aria-controls` | Item trigger | `IDREF` | Points to the adjacent panel. |
| `aria-expanded` | Item trigger | `"true" | "false"` | Reflects panel visibility. |
| `disabled` | Item trigger | `present | absent` | Uses native Button disabledness. |
| `hidden` | Closed panel | `present | absent` | Removes closed content from rendering and accessibility. |
| `inert` | Closed panel | `present | absent` | Prevents programmatic closed-panel interaction. |
### Selectors
Selectors for the DOM nodes in the components that you can use for CSS, inspection, and
testing.
#### CNavigationMenu selectors
| Selector | Element | Purpose |
|---|---|---|
| `[data-citry-ui-part="navigation-menu"]` | nav | Root style and attrs destination. |
| `[data-citry-ui-part="list"]` | ul | Direct child collection. |
| `[data-citry-ui-part="link-item"]` | Link li | Link list item. |
| `[data-citry-ui-part="link"]` | Native a | Navigation destination. |
| `[data-citry-ui-part="item"]` | Disclosure li | Item state boundary. |
| `[data-citry-ui-part="trigger"]` | Native Button | Disclosure control. |
| `[data-citry-ui-part="indicator"]` | Decorative span | Open-state chevron. |
| `[data-citry-ui-part="panel"]` | Neutral div | Rich navigation content. |
### Interfaces
Aliases and data shapes referenced above.
#### Input type aliases
| Interface | Definition |
|---|---|
| `CClassValue` | `str | Mapping[str, bool] | Sequence[CClassValue]` |
| `CStyleValue` | `str | Mapping[str, str | int | float | bool | None] | Sequence[CStyleValue]` |
| `CNavigationMenuOrientation` | `Literal["horizontal", "vertical"]` |
| `CNavigationMenuVariant` | `Literal["plain", "surface"]` |
| `CNavigationMenuSize` | `Literal["sm", "md", "lg"]` |
#### `CNavigationMenuDefaultSlotData`
Empty dataclass: `{}`.
#### `CNavigationMenuLinkDefaultSlotData`
Empty dataclass: `{}`.
#### `CNavigationMenuItemLabelSlotData`
Empty dataclass: `{}`.
#### `CNavigationMenuItemDefaultSlotData`
Empty dataclass: `{}`.
#### `CNavigationMenuValueChangeDetail`
| Field | Type | Default | Meaning |
|---|---|---|---|
| `value` | `str | None` | - | Requested open value. |
| `previousValue` | `str | None` | - | Previously effective value. |
| `reason` | `string` | - | Request source. |
| `controlled` | `boolean` | - | Whether a supplied client value owns state. |
| `forced` | `boolean` | - | Whether safety close overrides control. |
| `source` | `EventTarget | null` | - | Browser source. |
### Translation keys
-
---
# Pagination
Source: https://citry.dev/ui-library/components/pagination/
# Pagination
Use `CPagination` to move through a finite sequence while preserving native URLs or browser-local state.
## Pagination at a glance
### Pagination at a glance
[Open the rendered preview](/ui-library/components/pagination/_previews/at-a-glance/)
````citry
import citry_ui
from citry import Component, citry
citry.register_library(citry_ui)
class PaginationGlance(Component):
template = ' '
preview = PaginationGlance()
preview # noqa: B018
````
## Navigate with links
Put `{page}` in `href`. Server output then works before JavaScript and remains shareable.
### Navigate with page links
[Open the rendered preview](/ui-library/components/pagination/_previews/links/)
````citry
import citry_ui
from citry import Component, citry
citry.register_library(citry_ui)
class LinkPagination(Component):
template = ' '
preview = LinkPagination()
preview # noqa: B018
````
## Control the current page in the browser
Omit `href` for Button controls. Client inputs are passed with `$c-props="{...}"`.
Rebuilt Button ranges retain the server locale even without browser i18n; a client-enabled i18n provider also updates recreated labels when its locale changes.
### Control Pagination in the browser
[Open the rendered preview](/ui-library/components/pagination/_previews/controlled/)
````citry
import citry_ui
from citry import Component, citry
citry.register_library(citry_ui)
class ControlledPagination(Component):
template = """
Plate of 18
page = next }"
/>
"""
preview = ControlledPagination()
preview # noqa: B018
````
## Compact long ranges
`siblings` keeps pages around the current page. `boundaries` keeps pages at both ends.
### Compact long page ranges
[Open the rendered preview](/ui-library/components/pagination/_previews/ranges/)
````citry
import citry_ui
from citry import Component, citry
citry.register_library(citry_ui)
class PaginationRanges(Component):
template = """
"""
preview = PaginationRanges()
preview # noqa: B018
````
## Add edge controls
### Choose Pagination controls
[Open the rendered preview](/ui-library/components/pagination/_previews/controls/)
````citry
import citry_ui
from citry import Component, citry
citry.register_library(citry_ui)
class PaginationControls(Component):
template = """
"""
preview = PaginationControls()
preview # noqa: B018
````
## Choose presentation
### Compare Pagination variants and sizes
[Open the rendered preview](/ui-library/components/pagination/_previews/presentation/)
````citry
import citry_ui
from citry import Component, citry
citry.register_library(citry_ui)
class PaginationPresentation(Component):
template = """
"""
preview = PaginationPresentation()
preview # noqa: B018
````
## Customize Pagination
### Customize Pagination
[Open the rendered preview](/ui-library/components/pagination/_previews/customization/)
````citry
import citry_ui
from citry import Component, citry
citry.register_library(citry_ui)
class PaginationCustomization(Component):
template = ' '
css = """
:where(.lunar-pages) {
--cui-pagination-current-background: light-dark(#6d28d9, #c4b5fd);
--cui-pagination-current-foreground: light-dark(white, #2e1065);
--cui-pagination-radius: 999px;
}
"""
preview = PaginationCustomization()
preview # noqa: B018
````
## Accessibility and behavior
Pagination is a named navigation landmark. Current page uses `aria-current="page"`. Links and Buttons keep native Tab and activation behavior; ellipses are inert.
## API reference
### Inputs
#### CPagination server inputs
Server inputs are passed in a template through ` ` or in Python through
`CPagination(...)`.
| Input | Type | Default | Effect |
|---|---|---|---|
| `pages` | `int` | required | Sets the finite page count; must be at least 1. |
| `page` | `int` | `1` | Sets the current page between 1 and pages. |
| `href` | `str | None` | `None` | Creates native links by replacing a required `{page}` placeholder; None creates client-owned Buttons. |
| `siblings` | `int` | `1` | Keeps this many pages on either side of the current page. |
| `boundaries` | `int` | `1` | Keeps this many pages at both sequence edges. |
| `show_controls` | `bool` | `True` | Renders previous and next controls. |
| `show_edges` | `bool` | `False` | Renders first and last controls. |
| `disabled` | `bool` | `False` | Disables all Button-mode controls and removes link navigation. |
| `variant` | `"soft" | "outline" | "plain"` ([`CPaginationVariant`](#pagination-interface-input-type-aliases-pagination-variant)) | `"soft"` | Selects visual treatment. |
| `size` | `"sm" | "md" | "lg"` ([`CPaginationSize`](#pagination-interface-input-type-aliases-pagination-size)) | `"md"` | Selects control geometry. |
| `label` | `str` | `"Pagination"` | Names the navigation landmark. |
| `page_label` | `str` | `"Page {page}"` | Labels numbered controls; must contain `{page}`. |
| `previous_label` | `str` | `"Previous page"` | Labels the previous control. |
| `next_label` | `str` | `"Next page"` | Labels the next control. |
| `first_label` | `str` | `"First page"` | Labels the first control. |
| `last_label` | `str` | `"Last page"` | Labels the last control. |
| `class_` | `CClassValue | None` ([`CClassValue`](#pagination-interface-input-type-aliases-class-value)) | `None` | Adds root classes. |
| `style` | `CStyleValue | None` ([`CStyleValue`](#pagination-interface-input-type-aliases-style-value)) | `None` | Adds root inline styles. |
| `attrs` | `Mapping[str, object] | None` | `None` | Adds copied trusted nav attributes without replacing naming, children, focus ownership, or runtime fields. |
#### CPagination client inputs
Client inputs are passed in the browser through the `$c-props="{ ... }"` attribute on
` `.
| Input | Type | Omitted behavior | Effect |
|---|---|---|---|
| `page` | `number | undefined` | Uses the server input. | Controls and rebuilds the current compact range while supplied. |
| `disabled` | `boolean | undefined` | Uses the server input. | Overrides local disabled state while valid and supplied. |
| `variant` | `"soft" | "outline" | "plain" | undefined` | Uses the server input. | Overrides visual treatment. |
| `size` | `"sm" | "md" | "lg" | undefined` | Uses the server input. | Overrides control geometry. |
| `onPageChange` | `((page: number, detail: CPaginationChangeDetail) => void) | undefined` | Uses the server input. | Runs before accepted Button state changes or native link navigation. |
### Slots
-
### Events
Component events are callback inputs supplied through `$c-props`. Native browser events
remain available through Alpine `@...` attributes.
#### CPagination events
| Event | Signature | Trigger and timing | Detail | Controlled and cancellation behavior |
|---|---|---|---|---|
| `onPageChange` | `(page: number, detail: CPaginationChangeDetail) => void` ([`CPaginationChangeDetail`](#pagination-interface-cpagination-change-detail)) | Enabled noncurrent control activation. | `{page, previousPage, kind, sourceEvent}` ([`CPaginationChangeDetail`](#pagination-interface-cpagination-change-detail)) | Reports the target before Button state change or link navigation; preventing sourceEvent prevents a link. |
### Methods
-
### CSS
CSS variables to theme the components. Set them on an ancestor or the component itself.
#### CPagination CSS variables
Apply these variables to `CPagination` or one of its ancestors.
| Variable | Type | Purpose | Default |
|---|---|---|---|
| `--cui-pagination-gap` | `length` | Control gap. | `0.35rem` |
| `--cui-pagination-control-size` | `length` | Minimum control width and height. | `Size-derived.` |
| `--cui-pagination-radius` | `length` | Control radius. | `0.55rem` |
| `--cui-pagination-foreground` | `color` | Resting foreground. | `CanvasText` |
| `--cui-pagination-background` | `color` | Resting background. | `transparent` |
| `--cui-pagination-border-color` | `color` | Outline border. | `Nested-scheme border color.` |
| `--cui-pagination-current-background` | `color` | Current-page background. | `Nested-scheme blue.` |
| `--cui-pagination-current-foreground` | `color` | Current-page foreground. | `Contrasting nested-scheme color.` |
| `--cui-pagination-disabled-opacity` | `number` | Disabled opacity. | `0.5` |
| `--cui-pagination-focus-ring` | `color` | Keyboard focus outline. | `Highlight` |
### Attributes
HTML attributes defined on the components that you can refer to for CSS, inspection, and
testing. Read-only.
#### CPagination attributes
| Attribute | Element | Type | Meaning |
|---|---|---|---|
| `aria-current` | Current page control | `"page"` | Identifies the current page. |
| `data-current` | Current page control | `present-or-absent` | Public current-page styling hook. |
| `data-page` | Control | `integer-string` | Target page. |
| `data-kind` | Control | `"page" | "previous" | "next" | "first" | "last"` | Control job. |
| `data-disabled` | Root | `present-or-absent` | Present while navigation is disabled. |
| `data-variant` | Root | `"soft" | "outline" | "plain"` | Visual treatment. |
| `data-size` | Root | `"sm" | "md" | "lg"` | Control geometry. |
### Selectors
Selectors for the DOM nodes in the components that you can use for CSS, inspection, and
testing.
#### CPagination selectors
| Selector | Element | Purpose |
|---|---|---|
| `[data-citry-ui-part="pagination"]` | nav root | Stable root and attrs destination. |
| `[data-citry-ui-part="list"]` | ul | Stable list layout. |
| `[data-citry-ui-part="control"]` | link or Button | Stable interactive target. |
| `[data-citry-ui-part="ellipsis"]` | inert span | Stable omitted-range marker. |
### Interfaces
Aliases and data shapes referenced above.
#### Input type aliases
| Interface | Definition |
|---|---|
| `CClassValue` | `str | Mapping[str, bool] | Sequence[CClassValue]` |
| `CStyleValue` | `str | Mapping[str, str | int | float | bool | None] | Sequence[CStyleValue]` |
| `CPaginationVariant` | `Literal["soft", "outline", "plain"]` |
| `CPaginationSize` | `Literal["sm", "md", "lg"]` |
#### `CPaginationChangeDetail`
| Field | Type | Default | Meaning |
|---|---|---|---|
| `page` | `number` | - | Requested page. |
| `previousPage` | `number` | - | Current page before activation. |
| `kind` | `"page" | "previous" | "next" | "first" | "last"` | - | Activated control job. |
| `sourceEvent` | `Event` | - | Native click event; prevent it to stop a link. |
### Translation keys
Catalog keys used by this family. An explicit component input or slot listed in Override
takes precedence over the catalog for that instance.
#### CPagination translation keys
| Key | Purpose | Variables | Override | Browser updates |
|---|---|---|---|---|
| `citry-ui-pagination-label` | Names the pagination navigation landmark. | `None` | `label` input | $c-tr updates `aria-label`. |
| `citry-ui-pagination-page` | Names each numbered page control. | `page: str` | `page_label` input | $c-tr handles server controls; recreated controls use a fixed server-translated pattern without client i18n and `i18n.bind()` with it. |
| `citry-ui-pagination-previous` | Names the previous-page control. | `None` | `previous_label` input | $c-tr or `i18n.bind()` updates `aria-label`. |
| `citry-ui-pagination-next` | Names the next-page control. | `None` | `next_label` input | $c-tr or `i18n.bind()` updates `aria-label`. |
| `citry-ui-pagination-first` | Names the first-page control. | `None` | `first_label` input | $c-tr or `i18n.bind()` updates `aria-label`. |
| `citry-ui-pagination-last` | Names the last-page control. | `None` | `last_label` input | $c-tr or `i18n.bind()` updates `aria-label`. |
---
# Sidebar
Source: https://citry.dev/ui-library/components/sidebar/
# Sidebar
Use `CSidebar` for persistent application navigation or complementary tools.
It gives header and footer content fixed positions around one scrollable region
and supports rail or off-canvas collapse.
When a header is present, it shares the first Row with the collapse toggle.
Only the rail width transition clips horizontal overflow while the fixed-width
inner panel moves behind it, so labels do not flash as one-character columns.
At rest, the collapsed panel uses the actual rail width, preserving complete
icon boxes instead of clipping expanded boxes at the rail edge. Arbitrary slot
text stays on one clipped line in the steady rail instead of wrapping into a
tall one-character column; mark content with
`data-citry-sidebar-expanded-only` when it should disappear entirely.
## Sidebar at a glance
### Sidebar at a glance
[Open the rendered preview](/ui-library/components/sidebar/_previews/at-a-glance/)
````citry
import citry_ui
from citry import Component, citry
citry.register_library(citry_ui)
class SidebarAtAGlance(Component):
template = """
"""
css = """
:where(.sidebar-layout) {
display: grid;
grid-template-columns: auto minmax(0, 1fr);
gap: 1.5rem;
min-block-size: 24rem;
}
:where(.sidebar-layout main) { padding: 1rem; }
"""
preview = SidebarAtAGlance()
preview # noqa: B018
````
## Compose navigation from List
Sidebar does not invent a second navigation-item API. Compose `CList` for
links, `CDisclosure` for expandable sections, and `CMenu` for command popovers.
When rail-collapsed, List text stays visually clipped but remains the accessible
name of each link.
### Compose Sidebar navigation
[Open the rendered preview](/ui-library/components/sidebar/_previews/navigation/)
````citry
import citry_ui
from citry import Component, citry
citry.register_library(citry_ui)
class SidebarNavigation(Component):
template = """
Atlas
Activity
Members
Settings
"""
preview = SidebarNavigation()
preview # noqa: B018
````
## Choose a collapse mode
`collapsible="rail"` keeps an icon-width navigation rail. `offcanvas` hides
the panel while retaining the native toggle. `none` renders a permanent region
and rejects `collapsed=True`.
### Compare Sidebar collapse modes
[Open the rendered preview](/ui-library/components/sidebar/_previews/collapse-modes/)
````citry
import citry_ui
from citry import Component, citry
citry.register_library(citry_ui)
class SidebarCollapseModes(Component):
template = """
"""
css = ":where(.sidebar-modes){display:flex;align-items:flex-start;gap:1rem;min-block-size:14rem}"
preview = SidebarCollapseModes()
preview # noqa: B018
````
## Control collapse state
Supply `collapsed` through `$c-props` to control it. The callback is a request;
keep or change your value to reject or accept it.
### Control Sidebar collapse
[Open the rendered preview](/ui-library/components/sidebar/_previews/controlled/)
````citry
import citry_ui
from citry import Component, citry
citry.register_library(citry_ui)
class SidebarControlled(Component):
template = """
No request yet
Controlled Sidebar content
"""
preview = SidebarControlled()
preview # noqa: B018
````
## Build sticky and floating Sidebars
Sticky Sidebars use `--cui-sidebar-sticky-offset` to leave room for an
application header. `variant="floating"` adds a contained border, radius, and
elevation without registering page-layout insets. Sticky positioning applies
an offset and a viewport-sized maximum; it does not force a fixed height, so a
preview iframe cannot enter a self-expanding height loop.
### Choose Sidebar presentation
[Open the rendered preview](/ui-library/components/sidebar/_previews/presentation/)
````citry
import citry_ui
from citry import Component, citry
citry.register_library(citry_ui)
class SidebarPresentation(Component):
template = """
Inspector
Long tool content scrolls independently between fixed regions.
Keep adding contextual controls here.
Apply
"""
preview = SidebarPresentation()
preview # noqa: B018
````
## Customize Sidebar
### Customize Sidebar
[Open the rendered preview](/ui-library/components/sidebar/_previews/customization/)
````citry
import citry_ui
from citry import Component, citry
citry.register_library(citry_ui)
class SidebarCustomization(Component):
template = """
Ocean lab
Public variables and parts customize the stable landmark.
"""
css = """
:where(.ocean-sidebar) {
--cui-sidebar-background: light-dark(#eff8ff, #102a43);
--cui-sidebar-border-color: light-dark(#84caff, #2e90fa);
--cui-sidebar-width: 18rem;
}
"""
preview = SidebarCustomization()
preview # noqa: B018
````
## Persistent Sidebar or mobile Drawer?
Sidebar remains in document layout and never traps focus, adds a scrim, or
locks page scrolling. For modal mobile navigation, render the same application
navigation component inside `CDrawer placement="inline-start"`. A future
AppShell can choose the responsive policy without changing either component.
## Accessibility and localization
Choose `tag="nav"` when the content is navigation and `aside` for complementary
tools. `label` is required. The native toggle owns `aria-controls` and
`aria-expanded`; Enter and Space work without a custom keyboard model. If an
off-canvas collapse would hide current focus, focus moves to the toggle first.
The Expand and Collapse labels are Citry UI catalog messages. Override them
with `expand_label` and `collapse_label`; overrides stay fixed while catalog
defaults react to a client locale switch.
## API reference
### Inputs
#### CSidebar server inputs
Server inputs are passed in a template through ` ` or in Python through
`CSidebar(...)`.
| Input | Type | Default | Effect |
|---|---|---|---|
| `id` | `str | None` | generated | Sets the landmark ID and bases the controlled panel ID. |
| `label` | `str` | required | Names the complementary or navigation landmark. |
| `tag` | `CSidebarTag` ([`CSidebarTag`](#sidebar-interface-tag)) | `"aside"` | Selects complementary aside or navigation nav semantics. |
| `collapsed` | `bool` | `False` | Sets initial expanded or collapsed state. |
| `collapsible` | `CSidebarCollapsible` ([`CSidebarCollapsible`](#sidebar-interface-collapsible)) | `"rail"` | Selects rail offcanvas or permanent behavior. |
| `side` | `CSidebarSide` ([`CSidebarSide`](#sidebar-interface-side)) | `"inline-start"` | Selects the logical page edge and border/toggle placement. |
| `variant` | `CSidebarVariant` ([`CSidebarVariant`](#sidebar-interface-variant)) | `"plain"` | Selects flush or floating surface treatment. |
| `size` | `CSidebarSize` ([`CSidebarSize`](#sidebar-interface-size)) | `"md"` | Selects the default expanded width. |
| `sticky` | `bool` | `False` | Sticks the Sidebar at the public block offset within its scroll container. |
| `expand_label` | `str` | `"Expand sidebar"` | Overrides the localized expanded-state action name. |
| `collapse_label` | `str` | `"Collapse sidebar"` | Overrides the localized collapsed-state action name. |
| `class_` | `CClassValue | None` ([`CClassValue`](#sidebar-interface-class-value)) | `None` | Adds classes to the native landmark. |
| `style` | `CStyleValue | None` ([`CStyleValue`](#sidebar-interface-style-value)) | `None` | Adds styles to the native landmark. |
| `attrs` | `Mapping[str, object] | None` | `None` | Adds copied allowed landmark attributes without replacing owned semantics state or identity. |
#### CSidebar client inputs
Client inputs are passed in the browser through the `$c-props="{ ... }"` attribute on
` `.
| Input | Type | Omitted behavior | Effect |
|---|---|---|---|
| `collapsed` | `boolean | null` | Releases control to the committed value. | Controls expanded or collapsed state. |
| `collapsible` | `CSidebarCollapsible` ([`CSidebarCollapsible`](#sidebar-interface-collapsible)) | Uses the server value. | Controls rail offcanvas or permanent behavior. |
| `side` | `CSidebarSide` ([`CSidebarSide`](#sidebar-interface-side)) | Uses the server value. | Controls logical placement. |
| `variant` | `CSidebarVariant` ([`CSidebarVariant`](#sidebar-interface-variant)) | Uses the server value. | Controls surface treatment. |
| `size` | `CSidebarSize` ([`CSidebarSize`](#sidebar-interface-size)) | Uses the server value. | Controls width profile. |
| `sticky` | `boolean` | Uses the server value. | Controls sticky positioning. |
| `onCollapsedChange` | `function` | No semantic collapse callback. | Receives native toggle requests. |
### Slots
Slots are passed as nested content or `` tags in a template, or through the
`slots={...}` argument in Python.
#### CSidebar slots
| Slot | Required | Data | Fallback |
|---|---|---|---|
| `default` | yes | `{}` ([`CSidebarDefaultSlotData`](#sidebar-interface-csidebar-default-slot-data)) | None. |
| `header` | no | `{}` ([`CSidebarHeaderSlotData`](#sidebar-interface-csidebar-header-slot-data)) | Omitted. |
| `footer` | no | `{}` ([`CSidebarFooterSlotData`](#sidebar-interface-csidebar-footer-slot-data)) | Omitted. |
| `toggle` | no | `{collapsed}` ([`CSidebarToggleSlotData`](#sidebar-interface-csidebar-toggle-slot-data)) | Decorative neutral panel glyph. |
### Events
Component events are callback inputs supplied through `$c-props`. Native browser events
remain available through Alpine `@...` attributes.
#### CSidebar events
| Event | Signature | Trigger and timing | Detail | Controlled and cancellation behavior |
|---|---|---|---|---|
| `onCollapsedChange` | `(collapsed: boolean, detail: CSidebarCollapsedChangeDetail) => void` ([`CSidebarCollapsedChangeDetail`](#sidebar-interface-csidebar-collapsed-change-detail)) | Native toggle activation requests a different state. | `{collapsed, previousCollapsed, controlled, source, sourceEvent}` ([`CSidebarCollapsedChangeDetail`](#sidebar-interface-csidebar-collapsed-change-detail)) | Uncontrolled state commits before notification; controlled state is request-only. |
### Methods
-
### CSS
CSS variables to theme the components. Set them on an ancestor or the component itself.
#### CSidebar CSS variables
Apply these variables to `CSidebar` or one of its ancestors.
| Variable | Type | Purpose | Default |
|---|---|---|---|
| `--cui-sidebar-width` | `length` | Expanded inline size overriding the selected profile. | `sm 14rem; md 16rem; lg 20rem` |
| `--cui-sidebar-rail-width` | `length` | Collapsed rail inline size. | `4rem` |
| `--cui-sidebar-background` | `color` | Landmark and toggle surface. | `Adaptive neutral` |
| `--cui-sidebar-foreground` | `color` | Sidebar text and icon color. | `CanvasText` |
| `--cui-sidebar-border-color` | `color` | Logical edge and floating border. | `Adaptive neutral` |
| `--cui-sidebar-shadow` | `shadow` | Floating surface elevation. | `Soft elevation` |
| `--cui-sidebar-radius` | `length` | Floating surface and toggle corner input. | `0.85rem` |
| `--cui-sidebar-padding` | `length` | Internal panel spacing. | `0.75rem` |
| `--cui-sidebar-gap` | `length` | Header content footer and offcanvas-trigger spacing. | `0.75rem` |
| `--cui-sidebar-toggle-size` | `length` | Native toggle target size. | `2.75rem` |
| `--cui-sidebar-focus-color` | `color` | Toggle focus outline. | `Highlight` |
| `--cui-sidebar-sticky-offset` | `length` | Block offset reserved above a sticky Sidebar. | `0px` |
### Attributes
HTML attributes defined on the components that you can refer to for CSS, inspection, and
testing. Read-only.
#### CSidebar attributes
| Attribute | Element | Type | Meaning |
|---|---|---|---|
| `aria-label` | Root landmark | `string` | Names the complementary or navigation region. |
| `data-collapsed` | Root landmark | `present | absent` | Marks effective collapsed state. |
| `data-collapsible` | Root landmark | `CSidebarCollapsible` ([`CSidebarCollapsible`](#sidebar-interface-collapsible)) | Mirrors collapse behavior. |
| `data-side` | Root landmark | `CSidebarSide` ([`CSidebarSide`](#sidebar-interface-side)) | Mirrors logical placement. |
| `data-variant` | Root landmark | `CSidebarVariant` ([`CSidebarVariant`](#sidebar-interface-variant)) | Mirrors surface treatment. |
| `data-size` | Root landmark | `CSidebarSize` ([`CSidebarSize`](#sidebar-interface-size)) | Mirrors width profile. |
| `data-sticky` | Root landmark | `present | absent` | Marks sticky positioning. |
| `data-has-header` | Root landmark | `present | absent` | Marks header anatomy so the header and toggle share the first Row. |
| `aria-controls` | Toggle Button | `IDREF` | Refers to the owned panel. |
| `aria-expanded` | Toggle Button | `boolean-string` | Reflects expanded state. |
| `data-citry-sidebar-expanded-only` | Authored descendant | `present | absent` | Hides authored content in rail mode. |
| `data-citry-sidebar-rail-only` | Authored descendant | `present | absent` | Shows authored accessible replacement only in rail mode. |
### Selectors
Selectors for the DOM nodes in the components that you can use for CSS, inspection, and
testing.
#### CSidebar selectors
| Selector | Element | Purpose |
|---|---|---|
| `[data-citry-ui-part="sidebar"]` | Native aside or nav root | State reflections and customization destination. |
| `[data-citry-ui-part="toggle"]` | Native Button | Collapse control. |
| `[data-citry-ui-part="toggle-icon"]` | Decorative span | Custom or fallback visual. |
| `[data-citry-ui-part="toggle-label"]` | Visually hidden span | Localized state-dependent accessible name. |
| `[data-citry-ui-part="panel"]` | Owned div | Full-width clipped transition and fixed/scroll region owner. |
| `[data-citry-ui-part="header"]` | Optional header | Fixed branding and controls. |
| `[data-citry-ui-part="content"]` | Scrollable div | Primary authored Sidebar content. |
| `[data-citry-ui-part="footer"]` | Optional footer | Fixed account status or actions. |
### Interfaces
Aliases and data shapes referenced above.
#### Input type aliases
| Interface | Definition |
|---|---|
| `CSidebarTag` | `Literal["aside", "nav"]` |
| `CSidebarCollapsible` | `Literal["rail", "offcanvas", "none"]` |
| `CSidebarSide` | `Literal["inline-start", "inline-end"]` |
| `CSidebarVariant` | `Literal["plain", "floating"]` |
| `CSidebarSize` | `Literal["sm", "md", "lg"]` |
| `CClassValue` | `str | Mapping[str, bool] | Sequence[CClassValue]` |
| `CStyleValue` | `str | Mapping[str, object] | Sequence[CStyleValue]` |
#### `CSidebarDefaultSlotData`
Empty dataclass: `{}`.
#### `CSidebarHeaderSlotData`
Empty dataclass: `{}`.
#### `CSidebarFooterSlotData`
Empty dataclass: `{}`.
#### `CSidebarToggleSlotData`
| Field | Type | Default | Meaning |
|---|---|---|---|
| `collapsed` | `bool` | - | Server-rendered initial collapsed state. |
#### `CSidebarCollapsedChangeDetail`
| Field | Type | Default | Meaning |
|---|---|---|---|
| `collapsed` | `bool` | - | Requested collapsed state. |
| `previousCollapsed` | `bool` | - | Effective state before the request. |
| `controlled` | `bool` | - | Whether client state currently controls collapse. |
| `source` | `activation` | - | Native toggle activation source. |
| `sourceEvent` | `Event` | - | Native click event. |
### Translation keys
Catalog keys used by this family. An explicit component input or slot listed in Override
takes precedence over the catalog for that instance.
#### CSidebar translation keys
| Key | Purpose | Variables | Override | Browser updates |
|---|---|---|---|---|
| `citry-ui-sidebar-expand` | Names the action that expands a collapsed Sidebar. | `None.` | `expand_label` | Stable `$c-tr` text binding follows client locale changes. |
| `citry-ui-sidebar-collapse` | Names the action that collapses an expanded Sidebar. | `None.` | `collapse_label` | Stable `$c-tr` text binding follows client locale changes. |
---
# Stepper
Source: https://citry.dev/ui-library/components/stepper/
# Stepper
Use `CStepper` for the progress and navigation surface of a finite workflow.
Compose the current panel, validation, and Previous/Next actions beside it so
application state has one owner.
## Stepper at a glance
### Stepper at a glance
[Open the rendered preview](/ui-library/components/stepper/_previews/at-a-glance/)
````citry
import citry_ui
from citry import Component, citry
citry.register_library(citry_ui)
class StepperAtAGlance(Component):
template = """
Profile
Security
Review
"""
preview = StepperAtAGlance()
preview # noqa: B018
````
## Navigate a linear workflow
Set `interactive` to render form-safe native Buttons. Linear mode permits the
current and completed Steps while future Steps remain unavailable.
### Navigate completed Steps
[Open the rendered preview](/ui-library/components/stepper/_previews/interactive/)
````citry
import citry_ui
from citry import Component, citry
citry.register_library(citry_ui)
class InteractiveStepper(Component):
template = """
active = next }"
>
Draft
Review
Publish
Current zero-based index:
"""
preview = InteractiveStepper()
preview # noqa: B018
````
## Allow non-linear navigation
### Navigate Steps in any order
[Open the rendered preview](/ui-library/components/stepper/_previews/nonlinear/)
````citry
import citry_ui
from citry import Component, citry
citry.register_library(citry_ui)
class NonlinearStepper(Component):
template = """
active = next }"
>
Identity
Preferences
Notifications
"""
preview = NonlinearStepper()
preview # noqa: B018
````
## Show workflow metadata
Optional descriptions and error state belong to each Step declaration.
### Show optional and error Steps
[Open the rendered preview](/ui-library/components/stepper/_previews/states/)
````citry
import citry_ui
from citry import Component, citry
citry.register_library(citry_ui)
class StepperStates(Component):
template = """
Delivery address
Saved
Payment
Check the card number
Gift message
Optional
"""
preview = StepperStates()
preview # noqa: B018
````
## Control the active Step
Client `active` is controlled while supplied. `onActiveChange` requests a new
zero-based index; the application decides whether to accept it.
### Control active workflow state
[Open the rendered preview](/ui-library/components/stepper/_previews/controlled/)
````citry
import citry_ui
from citry import Component, citry
citry.register_library(citry_ui)
class ControlledStepper(Component):
template = """
active = next }"
>
Workspace
Members
Permissions
Previous
Next
"""
preview = ControlledStepper()
preview # noqa: B018
````
## Compare orientation, size, and variant
### Compare Stepper presentation
[Open the rendered preview](/ui-library/components/stepper/_previews/presentation/)
````citry
import citry_ui
from citry import Component, citry
citry.register_library(citry_ui)
class StepperPresentation(Component):
template = """
Start Finish
Start Finish
Start Finish
"""
preview = StepperPresentation()
preview # noqa: B018
````
## Customize Stepper
Public variables and part selectors work from an ancestor or the Stepper root.
### Customize Stepper
[Open the rendered preview](/ui-library/components/stepper/_previews/customization/)
````citry
import citry_ui
from citry import Component, citry
citry.register_library(citry_ui)
class CustomizedStepper(Component):
css = """
.orchid-stepper {
--cui-stepper-active-color: #7f56d9;
--cui-stepper-complete-color: #039855;
--cui-stepper-radius: 1.25rem;
}
.orchid-stepper [data-citry-ui-part="label"] { letter-spacing: 0.02em; }
"""
template = """
Choose Prepare Deliver
"""
preview = CustomizedStepper()
preview # noqa: B018
````
## Accessibility and behavior
The root is a named navigation landmark with an ordered list. The current
Step uses `aria-current="step"`. Interactive Steps are ordinary
`button type="button"` controls, so Tab, Enter, Space, focus, disabledness, and
form safety remain native. Stepper does not implement a composite Arrow-key
model and does not render workflow panels.
## API reference
### Inputs
#### CStepper server inputs
Server inputs are passed in a template through ` ` or in Python through
`CStepper(...)`.
| Input | Type | Default | Effect |
|---|---|---|---|
| `label` | `str` | required | Supplies the accessible navigation landmark name. |
| `active` | `int` | `0` | Sets the zero-based initial active Step. |
| `interactive` | `bool` | `False` | Structurally renders eligible Step triggers as native Buttons. |
| `linear` | `bool` | `True` | Makes upcoming interactive Steps unavailable. |
| `disabled` | `bool` | `False` | Disables every interactive Step. |
| `orientation` | `"horizontal" | "vertical"` ([`CStepperOrientation`](#stepper-interface-cstepper-orientation)) | `"horizontal"` | Selects logical Step layout. |
| `variant` | `"plain" | "soft" | "outline"` ([`CStepperVariant`](#stepper-interface-cstepper-variant)) | `"plain"` | Selects surface treatment. |
| `size` | `"sm" | "md" | "lg"` ([`CStepperSize`](#stepper-interface-cstepper-size)) | `"md"` | Selects indicator and spacing geometry. |
| `class_` | `CClassValue | None` ([`CClassValue`](#stepper-interface-cstepper-class-value)) | `None` | Adds root classes. |
| `style` | `CStyleValue | None` ([`CStyleValue`](#stepper-interface-cstepper-style-value)) | `None` | Adds root inline styles. |
| `attrs` | `Mapping[str, object] | None` | `None` | Adds trusted root attributes without replacing owned semantics state visibility children or runtime. |
#### CStepper client inputs
Client inputs are passed in the browser through the `$c-props="{ ... }"` attribute on
` `.
| Input | Type | Omitted behavior | Effect |
|---|---|---|---|
| `active` | `int | null` | Uses uncontrolled committed state. | Controls the zero-based active Step; null releases control. |
| `linear` | `bool` | Uses the server value. | Reactively limits navigation to current and completed Steps. |
| `disabled` | `bool` | Uses the server value. | Reactively disables interactive Steps. |
| `orientation` | `"horizontal" | "vertical"` ([`CStepperOrientation`](#stepper-interface-cstepper-orientation)) | Uses the server value. | Reactively changes logical layout. |
| `variant` | `"plain" | "soft" | "outline"` ([`CStepperVariant`](#stepper-interface-cstepper-variant)) | Uses the server value. | Reactively changes surface treatment. |
| `size` | `"sm" | "md" | "lg"` ([`CStepperSize`](#stepper-interface-cstepper-size)) | Uses the server value. | Reactively changes geometry. |
| `onActiveChange` | `((active: number, detail: CStepperActiveChangeDetail) => void) | undefined` | No component callback runs. | Receives eligible different Step navigation requests. |
#### CStep server inputs
Server inputs are passed in a template through ` ` or in Python through
`CStep(...)`.
| Input | Type | Default | Effect |
|---|---|---|---|
| `disabled` | `bool` | `False` | Makes this Step unavailable when interactive. |
| `optional` | `bool` | `False` | Reflects optional workflow metadata. |
| `error` | `bool` | `False` | Reflects an application-owned error state. |
| `class_` | `CClassValue | None` ([`CClassValue`](#stepper-interface-cstepper-class-value)) | `None` | Adds classes to the concrete Step list item. |
| `style` | `CStyleValue | None` ([`CStyleValue`](#stepper-interface-cstepper-style-value)) | `None` | Adds inline styles to the concrete Step list item. |
| `attrs` | `Mapping[str, object] | None` | `None` | Adds trusted list-item attributes without replacing owned identity state or trigger behavior. |
### Slots
Slots are passed as nested content or `` tags in a template, or through the
`slots={...}` argument in Python.
#### CStepper slots
| Slot | Required | Data | Fallback |
|---|---|---|---|
| `default` | yes | `{}` ([`CStepperDefaultSlotData`](#stepper-interface-cstepper-default-slot-data)) | None. Requires at least two direct CStep declarations. |
#### CStep slots
| Slot | Required | Data | Fallback |
|---|---|---|---|
| `default` | yes | `{index, state, is_current, is_disabled}` ([`CStepDefaultSlotData`](#stepper-interface-cstep-default-slot-data)) | None. Supplies the Step label. |
| `description` | no | `{index, state, is_current, is_disabled}` ([`CStepDescriptionSlotData`](#stepper-interface-cstep-description-slot-data)) | Omitted. |
| `indicator` | no | `{index, state, is_current, is_disabled}` ([`CStepIndicatorSlotData`](#stepper-interface-cstep-indicator-slot-data)) | One-based ASCII Step number. |
### Events
Component events are callback inputs supplied through `$c-props`. Native browser events
remain available through Alpine `@...` attributes.
#### CStepper events
| Event | Signature | Trigger and timing | Detail | Controlled and cancellation behavior |
|---|---|---|---|---|
| `onActiveChange` | `(active: number, detail: CStepperActiveChangeDetail) => void` ([`CStepperActiveChangeDetail`](#stepper-interface-cstepper-active-change-detail)) | Eligible different Step activation. | `{active, previousActive, controlled, step, sourceEvent}` ([`CStepperActiveChangeDetail`](#stepper-interface-cstepper-active-change-detail)) | Requests navigation before an uncontrolled commit or controlled reconciliation. |
### Methods
-
### CSS
CSS variables to theme the components. Set them on an ancestor or the component itself.
#### CStepper CSS variables
Apply these variables to `CStepper` or one of its ancestors.
| Variable | Type | Purpose | Default |
|---|---|---|---|
| `--cui-stepper-gap` | `length` | Gap between Steps. | `sm: 0.5rem; md: 0.75rem; lg: 1rem` |
| `--cui-stepper-indicator-size` | `length` | Indicator inline and block size. | `sm: 1.625rem; md: 2rem; lg: 2.5rem` |
| `--cui-stepper-trigger-gap` | `length` | Gap between indicator and copy. | `0.625rem` |
| `--cui-stepper-radius` | `length` | Root and trigger corner radius input. | `0.75rem` |
| `--cui-stepper-active-color` | `color` | Current indicator color. | `light #175cd3; dark #93c5fd` |
| `--cui-stepper-complete-color` | `color` | Completed indicator color. | `light #067647; dark #6ce9a6` |
| `--cui-stepper-muted-color` | `color` | Upcoming indicator and description color. | `light #667085; dark #a4a7ae` |
| `--cui-stepper-background` | `color` | Root background. | `plain and outline transparent; soft subtle CanvasText mix` |
| `--cui-stepper-border-color` | `color` | Outline indicator and separator color. | `light #d0d5dd; dark #535862` |
| `--cui-stepper-focus-color` | `color` | Interactive trigger focus outline. | `Highlight` |
### Attributes
HTML attributes defined on the components that you can refer to for CSS, inspection, and
testing. Read-only.
#### CStepper attributes
| Attribute | Element | Type | Meaning |
|---|---|---|---|
| `aria-label` | Root nav | `string` | Names the workflow navigation landmark. |
| `data-active` | Root nav | `nonnegative-integer-string` | Mirrors effective active index. |
| `data-orientation` | Root nav | `horizontal | vertical` | Mirrors effective layout. |
| `data-interactive` | Root nav | `present-or-absent` | Present when Steps render native Button triggers. |
| `data-linear` | Root nav | `present-or-absent` | Present when upcoming Steps are unavailable. |
| `data-variant` | Root nav | `plain | soft | outline` | Mirrors effective surface treatment. |
| `data-size` | Root nav | `sm | md | lg` | Mirrors effective geometry. |
| `data-index` | Step li | `nonnegative-integer-string` | Exposes zero-based settled order. |
| `data-state` | Step li | `complete | current | upcoming` | Mirrors derived status. |
| `aria-current` | Current trigger | `step` | Identifies the current workflow Step. |
| `data-disabled` | Root or Step | `present-or-absent` | Reflects effective component or Step unavailability. |
| `data-optional` | Step li | `present-or-absent` | Reflects optional metadata. |
| `data-error` | Step li | `present-or-absent` | Reflects error metadata. |
### Selectors
Selectors for the DOM nodes in the components that you can use for CSS, inspection, and
testing.
#### CStepper selectors
| Selector | Element | Purpose |
|---|---|---|
| `[data-citry-ui-part="stepper"]` | Root nav | Stable root and attrs destination. |
| `[data-citry-ui-part="list"]` | Ordered list | Stable Step collection. |
| `[data-citry-ui-part="step"]` | Step list item | Stable declaration attrs destination and state surface. |
| `[data-citry-ui-part="trigger"]` | Button or span | Stable interactive or static Step surface. |
| `[data-citry-ui-part="indicator"]` | Decorative span | Stable Step marker. |
| `[data-citry-ui-part="copy"]` | Copy wrapper span | Stable label and description wrapper. |
| `[data-citry-ui-part="label"]` | Label span | Stable accessible label content. |
| `[data-citry-ui-part="description"]` | Optional description span | Stable described-by target. |
| `[data-citry-ui-part="separator"]` | Decorative span | Stable connector. |
### Interfaces
Aliases and data shapes referenced above.
#### Input type aliases
| Interface | Definition |
|---|---|
| `CClassValue` | `str | Mapping[str, bool] | Sequence[CClassValue]` |
| `CStyleValue` | `str | Mapping[str, object] | Sequence[CStyleValue]` |
| `CStepperOrientation` | `Literal["horizontal", "vertical"]` |
| `CStepperVariant` | `Literal["plain", "soft", "outline"]` |
| `CStepperSize` | `Literal["sm", "md", "lg"]` |
| `CStepState` | `Literal["complete", "current", "upcoming"]` |
| `CStepDescriptionSlotData` | `CStepDefaultSlotData` |
| `CStepIndicatorSlotData` | `CStepDefaultSlotData` |
#### `CStepperDefaultSlotData`
Empty dataclass: `{}`.
#### `CStepDefaultSlotData`
| Field | Type | Default | Meaning |
|---|---|---|---|
| `index` | `int` | - | Zero-based settled Step index. |
| `state` | `"complete" | "current" | "upcoming"` ([`CStepState`](#stepper-interface-cstep-state)) | - | Server-rendered status. |
| `is_current` | `bool` | - | Whether this Step is initially current. |
| `is_disabled` | `bool` | - | Whether this Step is initially unavailable. |
#### `CStepperActiveChangeDetail`
| Field | Type | Default | Meaning |
|---|---|---|---|
| `active` | `int` | - | Requested zero-based index. |
| `previousActive` | `int` | - | Prior effective index. |
| `controlled` | `bool` | - | Whether a client active value currently controls state. |
| `step` | `HTMLElement` | - | Activated Step list item. |
| `sourceEvent` | `Event` | - | Native click event. |
### Translation keys
-
---
# Tabs
Source: https://citry.dev/ui-library/components/tabs/
# Tabs
Switch between related views in place. `CTabs`, `CTab`, and `CTabPanel`
provide the ARIA structure, roving focus, activation modes, and controlled
selection.
## Tabs at a glance
Compare underline and pill treatments. Click a Tab or use the arrow keys. The
disabled **Crew** Tab shows the unavailable state.
### Tabs at a glance
[Open the rendered preview](/ui-library/components/tabs/_previews/at-a-glance/)
````citry
import citry_ui
from citry import Component, citry
citry.register_library(citry_ui)
class TabsAtAGlance(Component):
template = """
Deep-space radio
Europa Relay
Broadcast
Signals
Crew
Now transmitting: a mixtape for whatever is out there.
A repeating pulse crossed 1,200 light-years
Three notes, a pause, then whale song.
This relay is delightfully uncrewed.
Lunar greenhouse
Habitat Seven
Crops
Climate
Supplies
Leafy greens are thriving
The blue-spectrum lamps run for six more hours.
Humidity is holding at 62% during the daylight cycle.
The next seed-vault delivery arrives in three orbits.
"""
css = """
:where(.tabs-sampler) {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(min(100%, 20rem), 1fr));
gap: 1rem;
max-width: 64rem;
color: CanvasText;
font-family: ui-sans-serif, system-ui, sans-serif;
}
:where(.tabs-sampler__card) {
min-width: 0;
padding: 1.25rem;
border: 1px solid;
border-radius: 0.875rem;
box-shadow: 0 0.75rem 2rem rgb(15 23 42 / 10%);
}
:where(.tabs-sampler__card--cosmic) {
--cui-tabs-accent: light-dark(#5b21b6, #ddd6fe);
--cui-tabs-focus-color: light-dark(#6d28d9, #c4b5fd);
--cui-tabs-active-background: light-dark(#ffffffb8, #2e1065b8);
border-color: light-dark(#c4b5fd, #6d28d9);
background: Canvas;
}
:where(.tabs-sampler__card--greenhouse) {
--cui-tabs-accent: light-dark(#0f766e, #5eead4);
--cui-tabs-focus-color: light-dark(#0d9488, #2dd4bf);
--cui-tabs-active-background: light-dark(#f0fdf4cc, #042f2ecc);
border-color: light-dark(#5eead4, #0f766e);
background: Canvas;
}
:where(.tabs-sampler__card header) {
margin-block-end: 1rem;
}
:where(.tabs-sampler__card h2, .tabs-sampler__card p) {
margin-block: 0;
}
:where(.tabs-sampler__eyebrow) {
color: var(--cui-tabs-accent, LinkText);
font-size: 0.75rem;
font-weight: 700;
letter-spacing: 0.08em;
text-transform: uppercase;
margin-bottom: 0.5rem;
}
:where(.tabs-sampler__metric) {
display: grid;
gap: 0.25rem;
}
:where(.tabs-sampler__metric span) {
color: color-mix(in srgb, currentColor 72%, transparent);
}
"""
preview = TabsAtAGlance()
preview # noqa: B018
````
## Compose Tabs, Tab controls, and Panels
Compose one `CTabs` root from matching `CTab` and `CTabPanel` declarations.
### Night sky guide
[Open the rendered preview](/ui-library/components/tabs/_previews/night-sky-guide/)
````citry
import citry_ui
from citry import Component, citry
citry.register_library(citry_ui)
class NightSkyGuide(Component):
template = """
Field guide
The night sky
Current topic:
planets
{
selected = value;
},
}"
>
Planets
Nebulae
Galaxies
Finding planets
Look for steady points of light. Planets usually twinkle less than stars.
Finding nebulae
Dark skies and a telescope reveal clouds of gas and dust.
Finding galaxies
From a dark site, the Andromeda Galaxy is visible without a telescope.
"""
css = """
:where(.night-sky-guide) {
--cui-tabs-accent: light-dark(#4338ca, #a5b4fc);
--cui-tabs-focus-color: light-dark(#4f46e5, #818cf8);
--cui-tabs-active-background: light-dark(#eef2ff, #1e1b4b);
max-width: 44rem;
padding: 1.5rem;
border: 1px solid light-dark(#a5b4fc, #4338ca);
border-radius: 0.875rem;
background: Canvas;
color: CanvasText;
font-family: ui-sans-serif, system-ui, sans-serif;
box-shadow: 0 1rem 2.5rem rgb(15 23 42 / 12%);
}
:where(.night-sky-guide header) {
margin-block-end: 1.25rem;
}
:where(.night-sky-guide h2, .night-sky-guide h3, .night-sky-guide p) {
margin-block: 0 0.5rem;
}
:where(.night-sky-guide__eyebrow) {
color: var(--cui-tabs-accent);
font-size: 0.75rem;
font-weight: 700;
letter-spacing: 0.08em;
text-transform: uppercase;
}
:where(.night-sky-guide output) {
font-weight: 700;
}
"""
preview = NightSkyGuide()
preview # noqa: B018
````
```citry-html
Planets
Nebulae
Worlds orbiting stars
Clouds of gas and dust
```
Each component has one job:
- `CTabs` owns selection and configuration, renders the root and the single
accessibly named `role="tablist"`, and groups the generated controls.
- `CTab` declares one value and its native Tab Button content.
- `CTabPanel` declares the view paired with that value.
Place the Tab declarations first, followed by their matching Panels. Tab
values are non-empty and unique, Panel values are non-empty and unique, and
both value sets must match. The initial value must identify an enabled Tab.
Provide either `aria_label` or `aria_labelledby` on `CTabs` to name the
generated Tab list.
`CTab` and `CTabPanel` are declarations, not standalone rendered components.
`CTabs` collects them before it renders the final Tab list and Panels. Using a
declaration outside `CTabs` fails. The default slot may contain formatting
whitespace, control flow, and transparent components, but no other rendered
HTML. This lets `CTabs` generate one correct semantic list without asking you
to maintain a structural-only list component.
## Try the configuration
Change accent, variant, density, orientation, alignment, growth, focus looping,
and disabled state. The controls use public CSS variables and `$c-props`.
### Configure Tabs
[Open the rendered preview](/ui-library/components/tabs/_previews/configuration/)
````citry
import citry_ui
from citry import Component, citry
citry.register_library(citry_ui)
class TabsConfiguration(Component):
template = """
Pocket reference
Solar system field guide
Current world:
mercury
{
selected = value;
},
}"
>
Mercury
Europa
Titan
Mercury
A cratered world with sharp swings between day and night.
Europa
An icy moon with evidence of a vast ocean beneath its surface.
Titan
A hazy moon with rivers and lakes of liquid methane.
"""
css = """
:where(.tabs-configurator) {
max-width: 64rem;
padding: 1.25rem;
border: 1px solid color-mix(in srgb, var(--cui-tabs-accent) 54%, transparent);
border-radius: 0.875rem;
background: Canvas;
color: CanvasText;
font-family: ui-sans-serif, system-ui, sans-serif;
box-shadow: 0 0.75rem 2rem rgb(15 23 42 / 10%);
}
:where(.tabs-configurator > header) {
margin-block-end: 1rem;
}
:where(.tabs-configurator h2, .tabs-configurator h3, .tabs-configurator p) {
margin-block: 0 0.5rem;
}
:where(.tabs-configurator__eyebrow) {
color: var(--cui-tabs-accent);
font-size: 0.75rem;
font-weight: 700;
letter-spacing: 0.08em;
text-transform: uppercase;
}
:where(.tabs-configurator__stage) {
min-width: 0;
}
:where(.tabs-configurator__status) {
color: color-mix(in srgb, currentColor 72%, transparent);
}
"""
preview_controls = (
{
"name": "accent",
"label": "Accent",
"type": "select",
"default": "violet",
"options": (
("violet", "Violet"),
("coral", "Coral"),
("teal", "Teal"),
("pink", "Pink"),
),
},
{
"name": "variant",
"label": "Variant",
"type": "select",
"default": "underline",
"options": (("underline", "Underline"), ("pill", "Pill")),
},
{
"name": "density",
"label": "Density",
"type": "select",
"default": "default",
"options": (
("default", "Default"),
("comfortable", "Comfortable"),
("compact", "Compact"),
),
},
{
"name": "orientation",
"label": "Orientation",
"type": "select",
"default": "horizontal",
"options": (("horizontal", "Horizontal"), ("vertical", "Vertical")),
},
{
"name": "align",
"label": "Alignment",
"type": "select",
"default": "start",
"options": (("start", "Start"), ("center", "Center"), ("end", "End")),
},
{
"name": "grow",
"label": "Grow to fill space",
"type": "checkbox",
"default": False,
},
{
"name": "loop",
"label": "Loop keyboard focus",
"type": "checkbox",
"default": True,
},
{
"name": "disabled",
"label": "Disable all Tabs",
"type": "checkbox",
"default": False,
},
)
preview = TabsConfiguration()
preview # noqa: B018
````
## Choose a variant
Use `underline` for low-emphasis navigation. Use `pill` when the choices need a
contained track and stronger selected state.
### Compare Tabs variants
[Open the rendered preview](/ui-library/components/tabs/_previews/variants/)
````citry
import citry_ui
from citry import Component, citry
citry.register_library(citry_ui)
class TabsVariants(Component):
template = """
Low-emphasis navigation
Underline
Orbit
Surface
Weather
Mars completes one orbit in roughly 687 Earth days.
Iron minerals give the surface its familiar red color.
Thin clouds and planet-wide dust storms shape the sky.
Orbit
Surface
Weather
Mars completes one orbit in roughly 687 Earth days.
Iron minerals give the surface its familiar red color.
Thin clouds and planet-wide dust storms shape the sky.
"""
css = """
:where(.tabs-variants) {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(min(100%, 19rem), 1fr));
gap: 1rem;
max-width: 64rem;
color: CanvasText;
font-family: ui-sans-serif, system-ui, sans-serif;
}
:where(.tabs-variants__card) {
--cui-tabs-accent: light-dark(#4338ca, #a5b4fc);
--cui-tabs-focus-color: light-dark(#4f46e5, #818cf8);
min-width: 0;
padding: 1.25rem;
border: 1px solid color-mix(in srgb, var(--cui-tabs-accent) 46%, transparent);
border-radius: 0.75rem;
background: Canvas;
}
:where(.tabs-variants__card--pill) {
--cui-tabs-accent: light-dark(#0f766e, #5eead4);
--cui-tabs-focus-color: light-dark(#0d9488, #2dd4bf);
--cui-tabs-active-background: light-dark(#f0fdfa, #042f2e);
}
:where(.tabs-variants__card header) {
margin-block-end: 0.75rem;
}
:where(.tabs-variants__card h2, .tabs-variants__card p) {
margin-block: 0;
margin-bottom: 0.5rem;
}
:where(.tabs-variants__card header p) {
color: color-mix(in srgb, currentColor 68%, transparent);
font-size: 0.75rem;
font-weight: 700;
letter-spacing: 0.06em;
text-transform: uppercase;
}
"""
preview = TabsVariants()
preview # noqa: B018
````
## Set density and available width
`default`, `comfortable`, and `compact` change Tab height and padding. Enable
equal width when every Tab should share the available main-axis space.
### Compare density and growth
[Open the rendered preview](/ui-library/components/tabs/_previews/density-and-growth/)
````citry
import citry_ui
from citry import Component, citry
citry.register_library(citry_ui)
class TabsDensityAndGrowth(Component):
template = """
Default
Orbit
Surface
Signals
Track the object's path across the sky.
Compare reflected-light surface features.
Review the latest radio observations.
Comfortable
Orbit
Surface
Signals
Track the object's path across the sky.
Compare reflected-light surface features.
Review the latest radio observations.
Compact
Orbit
Surface
Signals
Track the object's path across the sky.
Compare reflected-light surface features.
Review the latest radio observations.
"""
css = """
:where(.tabs-density) {
--cui-tabs-accent: light-dark(#4338ca, #a5b4fc);
--cui-tabs-focus-color: light-dark(#4f46e5, #818cf8);
display: grid;
gap: 1rem;
max-width: 64rem;
color: CanvasText;
font-family: ui-sans-serif, system-ui, sans-serif;
}
:where(.tabs-density__row) {
display: grid;
grid-template-columns: 7rem minmax(0, 1fr);
gap: 1rem;
align-items: start;
min-width: 0;
padding: 1rem;
border: 1px solid color-mix(in srgb, currentColor 16%, transparent);
border-radius: 0.75rem;
background: Canvas;
}
:where(.tabs-density__row h2) {
margin-block: 0.65rem 0;
color: color-mix(in srgb, currentColor 72%, transparent);
font-size: 0.875rem;
}
@media (max-width: 34rem) {
:where(.tabs-density__row) {
grid-template-columns: minmax(0, 1fr);
}
:where(.tabs-density__row h2) {
margin-block: 0;
}
}
"""
preview_controls = (
{
"name": "grow",
"label": "Make Tabs equal width",
"type": "checkbox",
"default": False,
},
)
preview = TabsDensityAndGrowth()
preview # noqa: B018
````
## Align and orient Tabs
Alignment follows the main axis. Vertical orientation moves the Tab list beside
the active Panel and switches keyboard movement to Up and Down.
### Align and orient Tabs
[Open the rendered preview](/ui-library/components/tabs/_previews/alignment-and-orientation/)
````citry
import citry_ui
from citry import Component, citry
citry.register_library(citry_ui)
class TabsAlignmentAndOrientation(Component):
template = """
Observatory catalog
Targets for tonight
Stars
Planets
Nebulae
Compare color, brightness, and spectral class.
Follow bright worlds as they move against the stars.
Find emission and reflection clouds in dark skies.
"""
css = """
:where(.tabs-layout) {
--cui-tabs-accent: light-dark(#0f766e, #5eead4);
--cui-tabs-focus-color: light-dark(#0d9488, #2dd4bf);
max-width: 52rem;
min-width: 0;
padding: 1.25rem;
border: 1px solid color-mix(in srgb, var(--cui-tabs-accent) 46%, transparent);
border-radius: 0.75rem;
background: Canvas;
color: CanvasText;
font-family: ui-sans-serif, system-ui, sans-serif;
}
:where(.tabs-layout header) {
margin-block-end: 1rem;
}
:where(.tabs-layout h2, .tabs-layout p) {
margin-block: 0;
}
:where(.tabs-layout header p) {
color: var(--cui-tabs-accent);
font-size: 0.75rem;
font-weight: 700;
letter-spacing: 0.06em;
text-transform: uppercase;
}
:where(.tabs-eyebrow) {
color: var(--cui-tabs-accent, LinkText);
font-size: 0.75rem;
font-weight: 700;
letter-spacing: 0.08em;
text-transform: uppercase;
margin-bottom: 0.5rem;
}
"""
preview_controls = (
{
"name": "orientation",
"label": "Orientation",
"type": "select",
"default": "horizontal",
"options": (("horizontal", "Horizontal"), ("vertical", "Vertical")),
},
{
"name": "align",
"label": "Alignment",
"type": "select",
"default": "start",
"options": (("start", "Start"), ("center", "Center"), ("end", "End")),
},
)
preview = TabsAlignmentAndOrientation()
preview # noqa: B018
````
## Control selection from JavaScript
Supplying client `value` makes selection controlled. A user request calls
`onValueChange`; the owner decides whether to commit the requested value.
### Control Tabs selection
[Open the rendered preview](/ui-library/components/tabs/_previews/controlled-selection/)
````citry
import citry_ui
from citry import Component, citry
citry.register_library(citry_ui)
class TabsControlledSelection(Component):
template = """
Show Mercury
Show Europa
Show Titan
Apply requests from Tabs
Selected
mercury
Last request
none
via
{
requested = value;
requestSource = detail.source;
if (applyRequests) {
current = value;
}
},
}"
>
Mercury
Europa
Titan
Mercury has the shortest year of any planet.
Europa's fractured ice may cover a deep ocean.
Titan has a dense atmosphere rich in nitrogen.
"""
css = """
:where(.tabs-controlled) {
--cui-tabs-accent: light-dark(#4338ca, #a5b4fc);
--cui-tabs-focus-color: light-dark(#4f46e5, #818cf8);
display: grid;
gap: 1rem;
max-width: 48rem;
min-width: 0;
padding: 1.25rem;
border: 1px solid color-mix(in srgb, var(--cui-tabs-accent) 46%, transparent);
border-radius: 0.75rem;
background: Canvas;
color: CanvasText;
font-family: ui-sans-serif, system-ui, sans-serif;
}
:where(.tabs-controlled h2, .tabs-controlled p) {
margin-block: 0;
}
:where(.tabs-controlled header p) {
color: var(--cui-tabs-accent);
font-size: 0.75rem;
font-weight: 700;
letter-spacing: 0.06em;
text-transform: uppercase;
}
:where(.tabs-controlled__owner-actions) {
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
}
:where(.tabs-controlled__owner-actions button) {
min-height: 2.25rem;
padding-inline: 0.75rem;
border: 1px solid color-mix(in srgb, currentColor 24%, transparent);
border-radius: 0.375rem;
background: Canvas;
color: CanvasText;
font: inherit;
cursor: pointer;
}
:where(.tabs-controlled__owner-actions button:focus-visible) {
outline: 0.1875rem solid var(--cui-tabs-focus-color);
outline-offset: 0.125rem;
}
:where(.tabs-controlled__commit) {
display: flex;
gap: 0.5rem;
align-items: center;
width: fit-content;
}
:where(.tabs-controlled__commit input) {
inline-size: 1rem;
block-size: 1rem;
margin: 0;
}
:where(.tabs-controlled__status) {
display: flex;
flex-wrap: wrap;
gap: 0.75rem 1.5rem;
margin: 0;
padding: 0.75rem;
border-radius: 0.5rem;
background: color-mix(in srgb, currentColor 7%, transparent);
}
:where(.tabs-controlled__status div) {
display: flex;
gap: 0.375rem;
}
:where(.tabs-controlled__status dt) {
color: color-mix(in srgb, currentColor 68%, transparent);
}
:where(.tabs-controlled__status dd) {
margin: 0;
font-weight: 700;
}
"""
preview = TabsControlledSelection()
preview # noqa: B018
````
```citry-html
{
currentTopic = value;
observationLog.record(detail);
},
}"
>
...
```
Omit client `value` for immediate uncontrolled selection. Removing a controlled
value continues uncontrolled from the last valid selection. An invalid value
keeps the last valid selection, reports a diagnostic, and still reports eligible
user requests.
Other supplied client inputs override their server inputs. Removing one restores
the server value. `null` is valid only for `direction`, where it restores
inherited browser direction. Other invalid values report a diagnostic and use
their server value.
!!! note
`onValueChange` runs only for a different enabled value. Initial selection
and owner updates do not run it. Return values do not cancel the request.
If client-owned DOM work removes the selected Tab, Tabs selects the next
enabled Tab at that position, then the previous enabled Tab, then the first
enabled Tab. If none remains, all Tabs and Panels become inactive and the
event does not run.
## Disable selection
Disable one `CTab` to keep it visible but unavailable. Disable `CTabs` to block
the whole group without losing the selected value.
### Disable Tabs
[Open the rendered preview](/ui-library/components/tabs/_previews/disabled-states/)
````citry
import citry_ui
from citry import Component, citry
citry.register_library(citry_ui)
class TabsDisabledStates(Component):
template = """
Launch windows
Inner-planet missions
Mercury
Venus
Mars
The next transfer study opens in September.
No launch window is available for this mission profile.
The next transfer study opens in November.
"""
css = """
:where(.tabs-disabled) {
--cui-tabs-accent: light-dark(#4338ca, #a5b4fc);
--cui-tabs-focus-color: light-dark(#4f46e5, #818cf8);
--cui-tabs-active-background: light-dark(#eef2ff, #1e1b4b);
max-width: 44rem;
min-width: 0;
padding: 1.25rem;
border: 1px solid color-mix(in srgb, var(--cui-tabs-accent) 46%, transparent);
border-radius: 0.75rem;
background: Canvas;
color: CanvasText;
font-family: ui-sans-serif, system-ui, sans-serif;
}
:where(.tabs-disabled header) {
margin-block-end: 1rem;
}
:where(.tabs-disabled h2, .tabs-disabled p) {
margin-block: 0;
}
:where(.tabs-disabled header p) {
color: var(--cui-tabs-accent);
font-size: 0.75rem;
font-weight: 700;
letter-spacing: 0.06em;
text-transform: uppercase;
}
:where(.tabs-eyebrow) {
color: var(--cui-tabs-accent, LinkText);
font-size: 0.75rem;
font-weight: 700;
letter-spacing: 0.08em;
text-transform: uppercase;
margin-bottom: 0.5rem;
}
"""
preview_controls = (
{
"name": "group_disabled",
"label": "Disable the whole group",
"type": "checkbox",
"default": False,
},
)
preview = TabsDisabledStates()
preview # noqa: B018
````
## Choose keyboard activation
Automatic activation selects as focus moves. Manual activation moves focus
first, then waits for Enter or Space.
### Compare keyboard activation
[Open the rendered preview](/ui-library/components/tabs/_previews/keyboard-activation/)
````citry
import citry_ui
from citry import Component, citry
citry.register_library(citry_ui)
class TabsKeyboardActivation(Component):
template = """
{
automaticValue = value;
},
}"
>
Orbit
Surface
Signals
The probe is completing its 18th orbit.
Surface imaging resumes after local sunrise.
The high-gain antenna is locked on Earth.
{
manualValue = value;
},
}"
>
Orbit
Surface
Signals
The probe is completing its 18th orbit.
Surface imaging resumes after local sunrise.
The high-gain antenna is locked on Earth.
"""
css = """
:where(.tabs-activation) {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(min(100%, 20rem), 1fr));
gap: 1rem;
max-width: 64rem;
color: CanvasText;
font-family: ui-sans-serif, system-ui, sans-serif;
}
:where(.tabs-activation__card) {
--cui-tabs-accent: light-dark(#4338ca, #a5b4fc);
--cui-tabs-focus-color: light-dark(#4f46e5, #818cf8);
min-width: 0;
padding: 1.25rem;
border: 1px solid color-mix(in srgb, var(--cui-tabs-accent) 46%, transparent);
border-radius: 0.75rem;
background: Canvas;
}
:where(.tabs-activation__card--manual) {
--cui-tabs-accent: light-dark(#0f766e, #5eead4);
--cui-tabs-focus-color: light-dark(#0d9488, #2dd4bf);
}
:where(.tabs-activation__card header) {
display: grid;
grid-template-columns: 1fr auto;
gap: 0.125rem 0.75rem;
align-items: end;
margin-block-end: 0.75rem;
}
:where(.tabs-activation__card h2, .tabs-activation__card p) {
margin-block: 0;
}
:where(.tabs-activation__card header p) {
grid-column: 1 / -1;
color: color-mix(in srgb, currentColor 68%, transparent);
font-size: 0.75rem;
}
:where(.tabs-activation__card output) {
color: var(--cui-tabs-accent);
font-weight: 700;
}
:where(.tabs-eyebrow) {
color: var(--cui-tabs-accent, LinkText);
font-size: 0.75rem;
font-weight: 700;
margin-bottom: 0.5rem;
}
"""
preview = TabsKeyboardActivation()
preview # noqa: B018
````
| Context | Key | Result |
|---|---|---|
| Horizontal LTR | Right / Left | Focus next / previous enabled Tab. |
| Horizontal RTL | Right / Left | Focus previous / next enabled Tab. |
| Vertical | Down / Up | Focus next / previous enabled Tab. |
| Either | Home / End | Focus first / last enabled Tab. |
| Manual activation | Enter / Space | Select the focused Tab. |
| Automatic activation | Arrow, Home, or End focus movement | Focus and select together. |
Horizontal Tabs do not consume Up or Down. Vertical Tabs do not consume Left
or Right. Disabled Tabs are skipped, and `loop=False` stops movement at either
end. Pointer activation selects and focuses the clicked enabled Tab.
Each Tab is a native `button type="button"` with `role="tab"`,
`aria-controls`, `aria-selected`, and roving `tabindex`. Each Panel has
`role="tabpanel"`, `aria-labelledby`, and `tabindex="0"`. Panels remain mounted;
inactive Panels receive `hidden`.
Without JavaScript, the server-selected Panel remains visible and all ARIA
relationships are valid, but the Tab Buttons do not switch Panels.
## Use long Tab lists
Long horizontal lists scroll inside the Tab-list surface. Pointer and keyboard
selection bring the active Tab into view. Overflow arrows and menus are not part
of the current component.
### Scroll a long Tab list
[Open the rendered preview](/ui-library/components/tabs/_previews/long-list/)
````citry
import citry_ui
from citry import Component, citry
citry.register_library(citry_ui)
class TabsLongList(Component):
template = """
Seven survey programs
Planetary observation queue
Scroll the Tab row to reach every survey.
Mercury geology
Venus cloud layers
Earth magnetosphere
Mars surface weather
Jupiter storm systems
Saturn ring survey
Outer-system objects
Map fresh impact craters near Mercury's equator.
Compare ultraviolet images of Venusian clouds.
Follow changes in Earth's magnetic environment.
Track dust and frost across the Martian surface.
Measure wind patterns around Jupiter's largest storms.
Resolve fine structure within Saturn's rings.
Search for faint objects beyond Neptune.
"""
css = """
:where(.tabs-overflow) {
--cui-tabs-accent: light-dark(#4338ca, #a5b4fc);
--cui-tabs-focus-color: light-dark(#4f46e5, #818cf8);
width: min(100%, 28rem);
min-width: 0;
padding: 1.25rem;
border: 1px solid color-mix(in srgb, var(--cui-tabs-accent) 46%, transparent);
border-radius: 0.75rem;
background: Canvas;
color: CanvasText;
font-family: ui-sans-serif, system-ui, sans-serif;
}
:where(.tabs-overflow header) {
margin-block-end: 1rem;
}
:where(.tabs-overflow h2, .tabs-overflow p) {
margin-block: 0;
}
:where(.tabs-overflow header p) {
color: var(--cui-tabs-accent);
font-size: 0.75rem;
font-weight: 700;
letter-spacing: 0.06em;
text-transform: uppercase;
}
:where(.tabs-overflow__hint) {
margin-block: 0 0.5rem;
color: color-mix(in srgb, currentColor 68%, transparent);
font-size: 0.8125rem;
}
:where(.tabs-eyebrow) {
color: var(--cui-tabs-accent, LinkText);
font-size: 0.75rem;
font-weight: 700;
margin-bottom: 0.5rem;
}
"""
preview = TabsLongList()
preview # noqa: B018
````
## Nest Tabs
Place nested Tabs inside a `CTabPanel`. Each root keeps independent selection,
focus, configuration, and callbacks.
### Nest Tabs
[Open the rendered preview](/ui-library/components/tabs/_previews/nested-tabs/)
````citry
import citry_ui
from citry import Component, citry
citry.register_library(citry_ui)
class TabsNested(Component):
template = """
Jupiter
Saturn
Moons
Atmosphere
Rings
Io, Europa, Ganymede, and Callisto are the largest moons.
Bands of clouds circle a deep hydrogen-rich atmosphere.
Jupiter has a faint ring system made mostly of dust.
Saturn's bright rings contain countless pieces of ice and rock.
"""
css = """
:where(.tabs-nested) {
--cui-tabs-accent: light-dark(#4338ca, #a5b4fc);
--cui-tabs-focus-color: light-dark(#4f46e5, #818cf8);
max-width: 52rem;
min-width: 0;
padding: 1.25rem;
border: 1px solid color-mix(in srgb, var(--cui-tabs-accent) 46%, transparent);
border-radius: 0.75rem;
background: Canvas;
color: CanvasText;
font-family: ui-sans-serif, system-ui, sans-serif;
}
:where(.tabs-nested > header) {
margin-block-end: 1rem;
}
:where(.tabs-nested h2, .tabs-nested p) {
margin-block: 0;
}
:where(.tabs-nested > header p) {
color: var(--cui-tabs-accent);
font-size: 0.75rem;
font-weight: 700;
letter-spacing: 0.06em;
text-transform: uppercase;
}
:where(.tabs-nested__inner) {
--cui-tabs-accent: light-dark(#0f766e, #5eead4);
--cui-tabs-focus-color: light-dark(#0d9488, #2dd4bf);
--cui-tabs-active-background: light-dark(#f0fdfa, #042f2e);
padding: 0.75rem;
border: 1px solid color-mix(in srgb, currentColor 14%, transparent);
border-radius: 0.625rem;
}
:where(.tabs-eyebrow) {
color: var(--cui-tabs-accent, LinkText);
font-size: 0.75rem;
font-weight: 700;
margin-bottom: 0.5rem;
}
"""
preview = TabsNested()
preview # noqa: B018
````
```citry-html
...
```
A Tab and Panel block access to their parent's Tabs context. Rendering Tabs
inside a Tab fails because native Buttons cannot contain interactive content. A
nested root also cannot sit directly among another root's declarations.
## Support text direction
Horizontal arrow keys follow visual direction. In RTL, Right moves toward the
previous declared Tab and Left moves toward the next. Vertical movement is
unchanged.
### Compare LTR and RTL Tabs
[Open the rendered preview](/ui-library/components/tabs/_previews/direction/)
````citry
import citry_ui
from citry import Component, citry
citry.register_library(citry_ui)
class TabsDirection(Component):
template = """
Left to right
Inner planets
Mercury
Venus
Earth
The closest planet to the Sun.
The second planet from the Sun.
Our home in the solar system.
من اليمين إلى اليسار
الكواكب الداخلية
عطارد
الزهرة
الأرض
الكوكب الأقرب إلى الشمس.
ثاني كوكب من الشمس.
موطننا في النظام الشمسي.
"""
css = """
:where(.tabs-direction) {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(min(100%, 19rem), 1fr));
gap: 1rem;
max-width: 64rem;
color: CanvasText;
font-family: ui-sans-serif, system-ui, sans-serif;
}
:where(.tabs-direction__card) {
--cui-tabs-accent: light-dark(#4338ca, #a5b4fc);
--cui-tabs-focus-color: light-dark(#4f46e5, #818cf8);
min-width: 0;
padding: 1.25rem;
border: 1px solid color-mix(in srgb, var(--cui-tabs-accent) 46%, transparent);
border-radius: 0.75rem;
background: Canvas;
}
:where(.tabs-direction__card--rtl) {
--cui-tabs-accent: light-dark(#0f766e, #5eead4);
--cui-tabs-focus-color: light-dark(#0d9488, #2dd4bf);
}
:where(.tabs-direction__card header) {
margin-block-end: 0.75rem;
}
:where(.tabs-direction__card h2, .tabs-direction__card p) {
margin-block: 0;
}
:where(.tabs-direction__card header p) {
color: var(--cui-tabs-accent);
font-size: 0.75rem;
font-weight: 700;
letter-spacing: 0.06em;
text-transform: uppercase;
}
:where(.tabs-eyebrow) {
color: var(--cui-tabs-accent, LinkText);
font-size: 0.75rem;
font-weight: 700;
margin-bottom: 0.5rem;
}
"""
preview = TabsDirection()
preview # noqa: B018
````
Set server `direction` to `ltr` or `rtl`, or leave it unset to inherit computed
browser direction. Client `direction: null` explicitly restores inheritance.
## Theme and customize Tabs
Tabs follow the surrounding `color-scheme`. Set documented `--cui-tabs-*`
variables on an ancestor or one Tabs root to customize color and geometry.
### Theme Tabs
[Open the rendered preview](/ui-library/components/tabs/_previews/theme-customization/)
````citry
import citry_ui
from citry import Component, citry
citry.register_library(citry_ui)
class TabsThemeCustomization(Component):
template = """
Light surface
Lunar atlas
Maria
Craters
Highlands
Dark plains formed by ancient volcanic flows.
Impact basins record billions of years of history.
Bright, heavily cratered terrain covers much of the Moon.
Maria
Craters
Highlands
Dark plains formed by ancient volcanic flows.
Impact basins record billions of years of history.
Bright, heavily cratered terrain covers much of the Moon.
"""
css = """
:where(.tabs-theme) {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(min(100%, 20rem), 1fr));
gap: 1rem;
max-width: 64rem;
font-family: ui-sans-serif, system-ui, sans-serif;
}
:where(.tabs-theme__card) {
--cui-tabs-accent: #1d4ed8;
--cui-tabs-border-color: #bfdbfe;
--cui-tabs-muted-color: #475569;
--cui-tabs-list-background: #eff6ff;
--cui-tabs-active-background: #ffffff;
--cui-tabs-hover-background: #dbeafe;
--cui-tabs-focus-color: #7c3aed;
--cui-tabs-radius: 0.75rem;
--cui-tabs-gap: 0.75rem;
--cui-tabs-panel-padding: 1rem 0.25rem 0.25rem;
color-scheme: light;
min-width: 0;
padding: 1.25rem;
border: 1px solid #bfdbfe;
border-radius: 0.75rem;
background: Canvas;
color: CanvasText;
}
:where(.tabs-theme__card--dark) {
--cui-tabs-accent: #67e8f9;
--cui-tabs-border-color: #155e75;
--cui-tabs-muted-color: #cbd5e1;
--cui-tabs-list-background: #083344;
--cui-tabs-active-background: #164e63;
--cui-tabs-hover-background: #0e7490;
--cui-tabs-focus-color: #f0abfc;
color-scheme: dark;
border-color: #155e75;
}
:where(.tabs-theme__card header) {
margin-block-end: 0.75rem;
}
:where(.tabs-theme__card h2, .tabs-theme__card p) {
margin-block: 0;
}
:where(.tabs-theme__card header p) {
color: var(--cui-tabs-accent);
font-size: 0.75rem;
font-weight: 700;
letter-spacing: 0.06em;
text-transform: uppercase;
}
:where(.tabs-eyebrow) {
color: var(--cui-tabs-accent, LinkText);
font-size: 0.75rem;
font-weight: 700;
margin-bottom: 0.5rem;
}
"""
preview = TabsThemeCustomization()
preview # noqa: B018
````
The two surfaces use the same component markup. Their explicit light and dark
schemes and public CSS variables supply every visual difference.
## API reference
### Inputs
#### CTabs server inputs
Server inputs are passed in a template through ` ` or in Python through
`CTabs(...)`.
| Input | Type | Default | Effect |
|---|---|---|---|
| `default_value` | `non-empty str` | required | Sets uncontrolled initial selection and the server fallback. |
| `value` | `str | None` | `None` | Selects the server-controlled value for this render and wins over `default_value`. |
| `activation` | `"automatic" | "manual"` ([`CTabsActivation`](#tabs-interface-ctabs-activation)) | `"automatic"` | Selects keyboard activation behavior. |
| `orientation` | `"horizontal" | "vertical"` ([`CTabsOrientation`](#tabs-interface-ctabs-orientation)) | `"horizontal"` | Sets layout, ARIA orientation, and keyboard axis. |
| `direction` | `"ltr" | "rtl" | None` ([`CTabsDirection`](#tabs-interface-ctabs-direction)) | `None` | Sets explicit direction or inherits computed browser direction. |
| `loop` | `bool` | `True` | Controls arrow-key wrapping. |
| `disabled` | `bool` | `False` | Disables every Tab without losing selected state. |
| `variant` | `"underline" | "pill"` ([`CTabsVariant`](#tabs-interface-ctabs-variant)) | `"underline"` | Selects the active treatment. |
| `density` | `"default" | "comfortable" | "compact"` ([`CTabsDensity`](#tabs-interface-ctabs-density)) | `"default"` | Selects Tab padding and minimum height. |
| `align` | `"start" | "center" | "end"` ([`CTabsAlign`](#tabs-interface-ctabs-align)) | `"start"` | Aligns Tabs on the list's main axis. |
| `grow` | `bool` | `False` | Makes Tabs share the available main-axis size. |
| `id` | `str | None` | generated | Sets the root and relationship prefix. Explicit values cannot be blank or contain ASCII whitespace. |
| `aria_label` | `str | None` | `None` | Directly names the generated Tab list. Required unless `aria_labelledby` is supplied. |
| `aria_labelledby` | `str | None` | `None` | Names the generated Tab list through another element's ID. Required unless `aria_label` is supplied. |
| `class_` | `str | Mapping[str, bool] | Sequence[CClassValue] | None` ([`CClassValue`](#tabs-interface-input-type-aliases-class-value)) | `None` | Adds Tabs-root classes from a string, conditional mapping, or nested sequence and merges them with `attrs`. |
| `style` | `str | Mapping[str, str | int | float | bool | None] | Sequence[CStyleValue] | None` ([`CStyleValue`](#tabs-interface-input-type-aliases-style-value)) | `None` | Adds Tabs-root inline styles from CSS text, a property mapping, or a nested sequence and merges them with `attrs`. |
| `attrs` | `dict[str, object] | None` | `None` | Adds allowed attributes to the Tabs root; prefer the top-level inputs for class and style. |
| `tab_list_attrs` | `dict[str, object] | None` | `None` | Adds allowed attributes to the generated `role="tablist"` element. |
#### CTabs client inputs
Client inputs are passed in the browser through the `$c-props="{ ... }"` attribute on
` `.
| Input | Type | Omitted behavior | Effect |
|---|---|---|---|
| `value` | `string` | Continues uncontrolled from the current selection. | Controls selection while supplied. |
| `activation` | `"automatic" | "manual"` ([`CTabsActivation`](#tabs-interface-ctabs-activation)) | Uses the server input. | Reactively controls keyboard activation. |
| `orientation` | `"horizontal" | "vertical"` ([`CTabsOrientation`](#tabs-interface-ctabs-orientation)) | Uses the server input. | Reactively controls layout, ARIA, and keyboard axis. |
| `direction` | `"ltr" | "rtl" | null` ([`CTabsDirection`](#tabs-interface-ctabs-direction)) | Uses the server input. | Sets direction; `null` explicitly restores inherited browser direction. |
| `loop` | `boolean` | Uses the server input. | Reactively controls wrapping. |
| `disabled` | `boolean` | Uses the server input. | Reactively controls root disabled state. |
| `variant` | `"underline" | "pill"` ([`CTabsVariant`](#tabs-interface-ctabs-variant)) | Uses the server input. | Reactively controls presentation. |
| `density` | `"default" | "comfortable" | "compact"` ([`CTabsDensity`](#tabs-interface-ctabs-density)) | Uses the server input. | Reactively controls geometry. |
| `align` | `"start" | "center" | "end"` ([`CTabsAlign`](#tabs-interface-ctabs-align)) | Uses the server input. | Reactively controls alignment. |
| `grow` | `boolean` | Uses the server input. | Reactively controls equal growth. |
#### CTab server inputs
Server inputs are passed in a template through ` ` or in Python through
`CTab(...)`.
| Input | Type | Default | Effect |
|---|---|---|---|
| `value` | `non-empty str` | required | Pairs this Tab with one Panel. |
| `disabled` | `bool` | `False` | Natively disables this Tab and removes it from selection and focus movement. |
| `class_` | `str | Mapping[str, bool] | Sequence[CClassValue] | None` ([`CClassValue`](#tabs-interface-input-type-aliases-class-value)) | `None` | Adds native Tab classes from a string, conditional mapping, or nested sequence and merges them with `attrs`. |
| `style` | `str | Mapping[str, str | int | float | bool | None] | Sequence[CStyleValue] | None` ([`CStyleValue`](#tabs-interface-input-type-aliases-style-value)) | `None` | Adds native Tab inline styles from CSS text, a property mapping, or a nested sequence and merges them with `attrs`. |
| `attrs` | `dict[str, object] | None` | `None` | Adds allowed attributes to the native Tab Button; prefer the top-level inputs for class and style. |
#### CTabPanel server inputs
Server inputs are passed in a template through ` ` or in Python through
`CTabPanel(...)`.
| Input | Type | Default | Effect |
|---|---|---|---|
| `value` | `non-empty str` | required | Pairs this Panel with one Tab. |
| `class_` | `str | Mapping[str, bool] | Sequence[CClassValue] | None` ([`CClassValue`](#tabs-interface-input-type-aliases-class-value)) | `None` | Adds Panel classes from a string, conditional mapping, or nested sequence and merges them with `attrs`. |
| `style` | `str | Mapping[str, str | int | float | bool | None] | Sequence[CStyleValue] | None` ([`CStyleValue`](#tabs-interface-input-type-aliases-style-value)) | `None` | Adds Panel inline styles from CSS text, a property mapping, or a nested sequence and merges them with `attrs`. |
| `attrs` | `dict[str, object] | None` | `None` | Adds allowed attributes to the Panel; prefer the top-level inputs for class and style. |
### Slots
Slots are passed as nested content or `` tags in a template, or through the
`slots={...}` argument in Python.
#### CTabs slots
| Slot | Required | Data | Fallback |
|---|---|---|---|
| `default` | yes | `{}` ([`CTabsDefaultSlotData`](#tabs-interface-ctabs-default-slot-data)) | none |
#### CTab slots
| Slot | Required | Data | Fallback |
|---|---|---|---|
| `default` | yes | `{value: str, is_selected: bool, is_disabled: bool}` ([`CTabDefaultSlotData`](#tabs-interface-ctab-default-slot-data)) | none |
#### CTabPanel slots
| Slot | Required | Data | Fallback |
|---|---|---|---|
| `default` | yes | `{value: str, is_selected: bool}` ([`CTabPanelDefaultSlotData`](#tabs-interface-ctabpanel-default-slot-data)) | none |
### Events
Component events are callback inputs supplied through `$c-props`. Native browser events
remain available through Alpine `@...` attributes.
#### CTabs events
| Event | Signature | Trigger and timing | Detail | Controlled and cancellation behavior |
|---|---|---|---|---|
| `onValueChange` | `(value: string, detail: object) => void` | A different enabled value is requested by pointer, keyboard, or removal. Runs before an uncontrolled commit; initial and owner updates are excluded. | `{value: string, previousValue: string, source: "pointer" | "keyboard" | "removal"}` | Controlled Tabs wait for `value` to update. Return values do not cancel the request. |
### Methods
-
### CSS
CSS variables to theme the components. Set them on an ancestor or the component itself.
#### CTabs CSS variables
Apply these variables to `CTabs` or one of its ancestors.
| Variable | Type | Purpose | Default |
|---|---|---|---|
| `--cui-tabs-accent` | `color` | Selected indicator and text. | `` `LinkText` `` |
| `--cui-tabs-border-color` | `color` | TabList divider. | `` 22% `currentColor` `` |
| `--cui-tabs-muted-color` | `color` | Inactive Tab text. | `` 68% `currentColor` `` |
| `--cui-tabs-list-background` | `color` | TabList or pill track. | `transparent; pill derives a 12% accent mix` |
| `--cui-tabs-active-background` | `color` | Selected pill background. | `` `Canvas` `` |
| `--cui-tabs-hover-background` | `color` | Enabled Tab hover background. | `` 8% `currentColor` `` |
| `--cui-tabs-focus-color` | `color` | Tab and Panel focus-visible outline. | `` `Highlight` `` |
| `--cui-tabs-radius` | `length` | Pill list and Tab radius basis. | `` `0.5rem` `` |
| `--cui-tabs-gap` | `length` | Gap between TabList and Panels. | `` `1rem` `` |
| `--cui-tabs-tab-inline-padding` | `length` | Tab logical inline padding. | `density-derived` |
| `--cui-tabs-tab-block-padding` | `length` | Tab logical block padding. | `density-derived` |
| `--cui-tabs-panel-padding` | `length` | Panel padding. | `` `1rem` `` |
### Attributes
HTML attributes defined on the components that you can refer to for CSS, inspection, and
testing. Read-only.
#### CTabs attributes
| Attribute | Element | Type | Meaning |
|---|---|---|---|
| `data-value` | Tabs root | `non-empty string or absent` | Effective selected value. |
| `data-activation` | Tabs root | `automatic | manual` | Effective activation behavior. |
| `data-orientation` | Tabs root | `horizontal | vertical` | Effective layout and keyboard axis. |
| `data-direction` | Tabs root | `ltr | rtl or absent` | Explicit effective direction. Absence means direction is inherited. |
| `data-loop` | Tabs root | `boolean attribute` | Whether focus movement wraps. |
| `data-density` | Tabs root | `default | comfortable | compact` | Effective density. |
| `data-variant` | Tabs root | `underline | pill` | Effective visual variant. |
| `data-align` | Tabs root | `start | center | end` | Effective Tab alignment. |
| `data-grow` | Tabs root | `boolean attribute` | Whether Tabs share available space. |
| `data-disabled` | Tabs root | `boolean attribute` | Whether the whole group is disabled. |
| `data-orientation` | Generated Tab list | `horizontal | vertical` | Effective list orientation. |
#### CTab attributes
| Attribute | Element | Type | Meaning |
|---|---|---|---|
| `data-state` | Tab Button | `active | inactive` | Whether this Tab is selected. |
| `data-value` | Tab Button | `non-empty string` | Immutable server-rendered Tab-to-Panel pairing identity. |
| `data-disabled` | Tab Button | `boolean attribute` | Effective per-Tab or root disabled state. |
#### CTabPanel attributes
| Attribute | Element | Type | Meaning |
|---|---|---|---|
| `data-state` | Tab Panel | `active | inactive` | Whether this Panel is selected. |
| `data-value` | Tab Panel | `non-empty string` | Immutable server-rendered Panel-to-Tab pairing identity. |
### Selectors
Selectors for the DOM nodes in the components that you can use for CSS, inspection, and
testing.
#### CTabs selectors
| Selector | Element | Purpose |
|---|---|---|
|
`[data-citry-ui-part="tabs"]` | Root `
` | Owns effective configuration and selected-value attributes. |
|
`[data-citry-ui-part="tab-list"]` | Generated `
` | Groups, names, and arranges the Tab controls. |
#### CTab selectors
| Selector | Element | Purpose |
|---|---|---|
| `[data-citry-ui-part="tab"]` | Native `` | Tab activation, focus, selected, hover, and disabled styling. |
#### CTabPanel selectors
| Selector | Element | Purpose |
|---|---|---|
|
`[data-citry-ui-part="tab-panel"]` | `
` | Contains one mounted Panel's content. |
### Interfaces
Aliases and data shapes referenced above.
#### Input type aliases
| Interface | Definition |
|---|---|
| `CClassValue` | `str | Mapping[str, bool] | Sequence[CClassValue]` |
| `CStyleValue` | `str | Mapping[str, str | int | float | bool | None] | Sequence[CStyleValue]` |
| `CTabsActivation` | `Literal["automatic", "manual"]` |
| `CTabsOrientation` | `Literal["horizontal", "vertical"]` |
| `CTabsDirection` | `Literal["ltr", "rtl"]` |
| `CTabsVariant` | `Literal["underline", "pill"]` |
| `CTabsDensity` | `Literal["default", "comfortable", "compact"]` |
| `CTabsAlign` | `Literal["start", "center", "end"]` |
#### `CTabsDefaultSlotData`
Empty dataclass: `{}`.
#### `CTabDefaultSlotData`
| Field | Type | Default | Meaning |
|---|---|---|---|
| `value` | `str` | - | This Tab's pairing value. |
| `is_selected` | `bool` | - | Whether this Tab is selected in the server render. |
| `is_disabled` | `bool` | - | Effective disabled state in the server render. |
#### `CTabPanelDefaultSlotData`
| Field | Type | Default | Meaning |
|---|---|---|---|
| `value` | `str` | - | This Panel's pairing value. |
| `is_selected` | `bool` | - | Whether this Panel is selected in the server render. |
### Translation keys
-
---
# Alert
Source: https://citry.dev/ui-library/components/alert/
# Alert
Use `CAlert` for persistent feedback about a page, section, action, or system
condition. Alert owns presentation and optional announcement semantics. Your
application owns visibility, dismissal, focus recovery, and retry behavior.
## Alert at a glance
Intent changes both color and icon shape, so meaning never depends on color
alone.
### Alert at a glance
[Open the rendered preview](/ui-library/components/alert/_previews/at-a-glance/)
````citry
import citry_ui
from citry import Component, citry
citry.register_library(citry_ui)
class AlertAtAGlance(Component):
template = """
Meteor shower tonight
Peak activity begins near 23:10.
Telescope aligned
Tracking error is below 0.2 arcseconds.
Cloud bank approaching
The western horizon may close after midnight.
Camera link lost
Reconnect before starting the next exposure.
"""
css = """
:where(.alert-glance) {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(min(100%, 18rem), 1fr));
gap: 0.875rem;
max-width: 72rem;
color: CanvasText;
font-family: ui-sans-serif, system-ui, sans-serif;
}
"""
preview = AlertAtAGlance()
preview # noqa: B018
````
## Compose an Alert
Write a message in the default slot. Add `title` when a condition needs a
short summary.
### Compose Alert content
[Open the rendered preview](/ui-library/components/alert/_previews/basic-alert/)
````citry
import citry_ui
from citry import Component, citry
citry.register_library(citry_ui)
class BasicAlerts(Component):
template = """
Comet viewing begins at 22:40.
Calibration complete
The spectrograph is ready for the first target.
"""
css = """
:where(.basic-alerts) {
display: grid;
gap: 0.875rem;
max-width: 46rem;
color: CanvasText;
font-family: ui-sans-serif, system-ui, sans-serif;
}
"""
preview = BasicAlerts()
preview # noqa: B018
````
```citry-html
Cloud cover approaching
The western ridge may disappear after midnight.
```
Compose the same Alert in Python:
```python
from citry_ui import CAlert
forecast = CAlert(
intent="warn",
slots={
"title": "Cloud cover approaching",
"default": "The western ridge may disappear after midnight.",
},
)
```
At least one of `title` or `default` is required. Alert does not choose a
heading rank; put the appropriate native heading in the title slot when the
Alert introduces a document section.
## Choose visual meaning
Use `info` for neutral context, `success` for completion, `warn` for a
condition that needs attention, and `error` for failure.
### Compare Alert intents
[Open the rendered preview](/ui-library/components/alert/_previews/intents/)
````citry
import citry_ui
from citry import Component, citry
citry.register_library(citry_ui)
class AlertIntents(Component):
class Kwargs:
pass
class Slots:
pass
template = """
{{ item[1] }}
{{ item[2] }}
"""
def template_data(
self,
kwargs: Kwargs, # noqa: ARG002
slots: Slots, # noqa: ARG002
) -> dict[str, object]:
return {
"alerts": (
("info", "Night plan ready", "Six targets fit the darkness window."),
("success", "Guide star acquired", "Tracking has settled on Vega."),
("warn", "Humidity rising", "Review the dome limit before continuing."),
("error", "Dome drive stopped", "Close the shutter manually."),
)
}
css = """
:where(.alert-intents) {
display: grid;
gap: 0.75rem;
max-width: 52rem;
color: CanvasText;
font-family: ui-sans-serif, system-ui, sans-serif;
}
"""
preview = AlertIntents()
preview # noqa: B018
````
`intent` is visual meaning, not urgency. Configure announcements separately.
## Choose emphasis
`soft` is the quiet default. Use `solid` for stronger prominence and `outline`
when the surrounding surface should remain visible.
### Compare Alert variants
[Open the rendered preview](/ui-library/components/alert/_previews/variants/)
````citry
import citry_ui
from citry import Component, citry
citry.register_library(citry_ui)
class AlertVariants(Component):
class Kwargs:
pass
class Slots:
pass
template = """
{{ variant[1] }} warning
High cirrus may reduce contrast on faint galaxies.
"""
def template_data(
self,
kwargs: Kwargs, # noqa: ARG002
slots: Slots, # noqa: ARG002
) -> dict[str, object]:
return {"variants": (("soft", "Soft"), ("solid", "Solid"), ("outline", "Outline"))}
css = """
:where(.alert-variants) {
display: grid;
gap: 0.75rem;
max-width: 52rem;
color: CanvasText;
font-family: ui-sans-serif, system-ui, sans-serif;
}
"""
preview = AlertVariants()
preview # noqa: B018
````
## Choose size
`sm`, `md`, and `lg` change spacing, text scale, icon geometry, and action gap.
### Compare Alert sizes
[Open the rendered preview](/ui-library/components/alert/_previews/sizes/)
````citry
import citry_ui
from citry import Component, citry
citry.register_library(citry_ui)
class AlertSizes(Component):
class Kwargs:
pass
class Slots:
pass
template = """
{{ size[1] }} Alert
The northern camera is ready.
"""
def template_data(
self,
kwargs: Kwargs, # noqa: ARG002
slots: Slots, # noqa: ARG002
) -> dict[str, object]:
return {"sizes": (("sm", "Small"), ("md", "Medium"), ("lg", "Large"))}
css = """
:where(.alert-sizes) {
display: grid;
gap: 0.75rem;
max-width: 48rem;
color: CanvasText;
font-family: ui-sans-serif, system-ui, sans-serif;
}
"""
preview = AlertSizes()
preview # noqa: B018
````
## Configure icons
The default icon follows intent. Set `icon=False` to hide it or pass a
registered `icon_name` for a fixed decorative glyph.
### Use automatic, hidden, and fixed icons
[Open the rendered preview](/ui-library/components/alert/_previews/icons/)
````citry
import citry_ui
from citry import Component, citry
citry.register_library(citry_ui)
class AlertIcons(Component):
template = """
Automatic success icon follows intent.
Icon hidden; the message still carries the meaning.
Fixed registered star icon stays constant when intent changes.
"""
css = """
:where(.alert-icons) {
display: grid;
gap: 0.75rem;
max-width: 50rem;
color: CanvasText;
font-family: ui-sans-serif, system-ui, sans-serif;
}
"""
preview = AlertIcons()
preview # noqa: B018
````
Icons are hidden from the accessibility tree. Put essential meaning in the
title or message.
## Add actions
Use the `actions` slot for links, Buttons, menus, or other related controls.
`actions_label` gives the controls a named group without adding another layout
wrapper.
### Add actions and own dismissal
[Open the rendered preview](/ui-library/components/alert/_previews/actions/)
````citry
import citry_ui
from citry import Component, citry
citry.register_library(citry_ui)
class AlertActions(Component):
template = """
Cloud cover approaching
The western ridge may disappear after midnight.
Open forecast
document
.getElementById('restore-observatory-notice')
.focus())"
>
Dismiss
Restore observatory notice
"""
css = """
:where(.alert-actions-demo) {
display: grid;
gap: 0.75rem;
max-width: 52rem;
color: CanvasText;
font-family: ui-sans-serif, system-ui, sans-serif;
}
:where(.alert-actions-demo > button) {
justify-self: start;
padding: 0.5rem 0.75rem;
border: 1px solid light-dark(#8da1bb, #687b97);
border-radius: 0.5rem;
background: Canvas;
color: CanvasText;
cursor: pointer;
}
"""
preview = AlertActions()
preview # noqa: B018
````
Alert has no close input or callback. The state owner hides or removes it and
chooses where focus goes when a focused action disappears. The example retains
the Alert with `x-show`; use a server rerender when dismissal must remove it.
## Configure Alert in the browser
Server inputs are passed in Python through `
` attributes or a
`CAlert(...)` composition call. Client inputs are passed in the browser through
`$c-props="{...}"`.
### Configure Alert
[Open the rendered preview](/ui-library/components/alert/_previews/configure/)
````citry
import citry_ui
from citry import Component, citry
citry.register_library(citry_ui)
class ConfigureAlert(Component):
template = """
Live configuration
Observation status
Tracking update
The guide camera is following the selected star.
"""
css = """
:where(.alert-configurator) {
display: grid;
gap: 1.25rem;
max-width: 52rem;
color: CanvasText;
font-family: ui-sans-serif, system-ui, sans-serif;
}
:where(.alert-configurator h2, .alert-configurator p) {
margin: 0;
}
:where(.alert-configurator header p) {
color: light-dark(#3758a6, #9db7ff);
font-size: 0.75rem;
font-weight: 700;
letter-spacing: 0.08em;
text-transform: uppercase;
}
"""
preview_controls = (
{
"name": "intent",
"label": "Intent",
"type": "select",
"default": "info",
"options": (
("info", "Info"),
("success", "Success"),
("warn", "Warn"),
("error", "Error"),
),
},
{
"name": "variant",
"label": "Variant",
"type": "select",
"default": "soft",
"options": (("soft", "Soft"), ("solid", "Solid"), ("outline", "Outline")),
},
{
"name": "size",
"label": "Size",
"type": "select",
"default": "md",
"options": (("sm", "Small"), ("md", "Medium"), ("lg", "Large")),
},
{
"name": "announce",
"label": "Announcement",
"type": "select",
"default": "off",
"options": (("off", "Off"), ("polite", "Polite"), ("assertive", "Assertive")),
},
{"name": "icon", "label": "Show icon", "type": "checkbox", "default": True},
)
preview = ConfigureAlert()
preview # noqa: B018
````
Client `intent`, `variant`, `size`, `announce`, and `icon` values override the
server fallback. Omit a value to return to that fallback. Invalid values never
acquire ownership.
## Choose announcement urgency
The default `announce="off"` adds no live-region role. Use `polite` for a
nonblocking update and `assertive` only when attention is immediate.
### Compare announcement modes
[Open the rendered preview](/ui-library/components/alert/_previews/announcements/)
````citry
import citry_ui
from citry import Component, citry
citry.register_library(citry_ui)
class AlertAnnouncements(Component):
template = """
Static observing instructions use no live-region role.
Exposure saved
Use polite urgency for a nonblocking update.
Shutter obstruction
Use assertive urgency only when attention is immediate.
"""
css = """
:where(.alert-announcements) {
display: grid;
gap: 0.75rem;
max-width: 52rem;
color: CanvasText;
font-family: ui-sans-serif, system-ui, sans-serif;
}
"""
preview = AlertAnnouncements()
preview # noqa: B018
````
Alert applies `status` or `alert` to the content wrapper, never the action
group. It does not guarantee that a populated Alert inserted in one operation
will be announced by every browser and assistive-technology pair. A queued,
reliable announcer needs a persistent owner.
## Customize the theme
Override public variables on an ancestor or one Alert. Use stable part
selectors for targeted rules.
### Theme observatory Alerts
[Open the rendered preview](/ui-library/components/alert/_previews/customization/)
````citry
import citry_ui
from citry import Component, citry
citry.register_library(citry_ui)
class AlertCustomization(Component):
template = """
Solar observatory
Coronal imaging pauses during the calibration sweep.
Radio observatory
The receiver array is synchronized.
"""
css = """
:where(.alert-themes) {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(min(100%, 20rem), 1fr));
gap: 1rem;
max-width: 60rem;
color: CanvasText;
font-family: ui-sans-serif, system-ui, sans-serif;
}
:where(.alert-themes article) {
display: grid;
gap: 0.75rem;
}
:where(.alert-themes h2) {
margin: 0;
font-size: 1rem;
}
:where(.alert-themes__solar) {
--cui-alert-background: light-dark(#fff8df, #30270b);
--cui-alert-border-color: light-dark(#d99d13, #ffd166);
--cui-alert-icon-color: light-dark(#9a6700, #ffd166);
}
:where(.radio-success[data-citry-ui-part="alert"]) {
--cui-alert-border-color: light-dark(#6d28d9, #c4b5fd);
--cui-alert-icon-color: light-dark(#6d28d9, #c4b5fd);
--cui-alert-radius: 1.25rem;
}
"""
preview = AlertCustomization()
preview # noqa: B018
````
`class_`, `style`, and `attrs` target the root. `actions_attrs` targets the
optional action wrapper. Unlayered consumer CSS overrides Citry UI defaults;
named layers follow the site-wide layer-order contract.
## Accessibility and trust
Alert never moves focus, adds a Tab stop, traps keyboard input, or handles
Escape. Authored actions keep native DOM and Tab order. Visual intent changes
icon shape as well as color.
Title and message content use ordinary Citry escaping. `actions_label` is
converted to plain text before attribute rendering. Registered icon names use
the packaged allowlist. `attrs`, `actions_attrs`, `class_`, and `style` remain
trusted authoring surfaces for unowned values; Alert rejects attributes and
directives that could replace its children, semantics, focus ownership,
public mirrors, or runtime markers.
## API reference
### Inputs
#### CAlert server inputs
Server inputs are passed in a template through `
` or in Python through
`CAlert(...)`.
| Input | Type | Default | Effect |
|---|---|---|---|
| `intent` | `"info" | "success" | "warn" | "error"` ([`CAlertIntent`](#alert-interface-alert-intent)) | `"info"` | Selects visual meaning, colors, and the automatic icon without choosing announcement urgency. |
| `variant` | `"soft" | "solid" | "outline"` ([`CAlertVariant`](#alert-interface-alert-variant)) | `"soft"` | Selects quiet, strong, or transparent visual emphasis. |
| `size` | `"sm" | "md" | "lg"` ([`CAlertSize`](#alert-interface-alert-size)) | `"md"` | Selects spacing, text scale, icon size, and action gap. |
| `announce` | `"off" | "polite" | "assertive"` ([`CAlertAnnounce`](#alert-interface-alert-announce)) | `"off"` | Applies no role, `status`, or `alert` to the content wrapper. It does not guarantee delivery by assistive technology. |
| `icon` | `bool` | `True` | Shows or hides the decorative automatic or fixed icon. |
| `icon_name` | `CIconName | None` ([`CIconName`](#alert-interface-alert-icon-name)) | `None` | Uses one registered fixed glyph. Omit it to let the icon follow intent. |
| `actions_label` | `non-whitespace str | None` | `None` | Names the optional action group and emits its owned `group` role; requires the actions slot. |
| `class_` | `str | Mapping[str, bool] | Sequence[CClassValue] | None` ([`CClassValue`](#alert-interface-alert-class-value)) | `None` | Adds root classes and merges them with `attrs`. |
| `style` | `str | Mapping[str, str | int | float | bool | None] | Sequence[CStyleValue] | None` ([`CStyleValue`](#alert-interface-alert-style-value)) | `None` | Adds root inline styles and merges them with `attrs`. |
| `attrs` | `Mapping[str, object] | None` | `None` | Adds trusted unowned root attributes. Alert semantics, children, structural Alpine directives, focus ownership, public mirrors, and runtime namespaces are reserved. |
| `actions_attrs` | `Mapping[str, object] | None` | `None` | Adds trusted unowned action-wrapper attributes. Group naming, focus, live-region, children, structural or initialization-suppressing directives, and runtime ownership are reserved; a nonempty mapping requires actions. |
#### CAlert client inputs
Client inputs are passed in the browser through the `$c-props="{ ... }"` attribute on
`
`.
| Input | Type | Omitted behavior | Effect |
|---|---|---|---|
| `intent` | `"info" | "success" | "warn" | "error"` ([`CAlertIntent`](#alert-interface-alert-intent)) | Uses the server input. | Controls visual meaning, colors, and the automatic icon. |
| `variant` | `"soft" | "solid" | "outline"` ([`CAlertVariant`](#alert-interface-alert-variant)) | Uses the server input. | Controls visual emphasis. |
| `size` | `"sm" | "md" | "lg"` ([`CAlertSize`](#alert-interface-alert-size)) | Uses the server input. | Controls geometry and text scale. |
| `announce` | `"off" | "polite" | "assertive"` ([`CAlertAnnounce`](#alert-interface-alert-announce)) | Uses the server input. | Controls the content wrapper's announcement role. |
| `icon` | `boolean` | Uses the server input. | Controls decorative icon visibility. |
### Slots
Slots are passed as nested content or `
` tags in a template, or through the
`slots={...}` argument in Python.
#### CAlert slots
| Slot | Required | Data | Fallback |
|---|---|---|---|
| `title` | no | `{}` ([`CAlertTitleSlotData`](#alert-interface-alert-title-slot-data)) | No title wrapper. At least title or default is required. |
| `default` | no | `{}` ([`CAlertDefaultSlotData`](#alert-interface-alert-default-slot-data)) | No message wrapper. At least title or default is required. |
| `actions` | no | `{}` ([`CAlertActionsSlotData`](#alert-interface-alert-actions-slot-data)) | No action group. |
### Events
-
### Methods
-
### CSS
CSS variables to theme the components. Set them on an ancestor or the component itself.
#### CAlert CSS variables
Apply these variables to `CAlert` or one of its ancestors.
| Variable | Type | Purpose | Default |
|---|---|---|---|
| `--cui-alert-background` | `color` | Root background. | `Variant- and intent-derived color.` |
| `--cui-alert-foreground` | `color` | Title, message, and action foreground. | `Variant- and intent-derived color.` |
| `--cui-alert-border-color` | `color` | Root boundary color. | `Variant- and intent-derived color.` |
| `--cui-alert-icon-color` | `color` | Automatic or fixed icon foreground. | `Intent color or solid foreground.` |
| `--cui-alert-border-width` | `length` | Root border width. | `1px` |
| `--cui-alert-radius` | `length` | Root corner radius. | `0.75rem` |
| `--cui-alert-padding` | `length` | Root block and inline padding. | `Size-derived spacing.` |
| `--cui-alert-gap` | `length` | Gap between indicator, content, and actions. | `Size-derived spacing.` |
| `--cui-alert-content-gap` | `length` | Space between title and message. | `Size-derived spacing.` |
| `--cui-alert-actions-gap` | `length` | Gap between direct action controls. | `Size-derived spacing.` |
| `--cui-alert-title-font-weight` | `number` | Title emphasis without choosing heading semantics. | `650` |
### Attributes
HTML attributes defined on the components that you can refer to for CSS, inspection, and
testing. Read-only.
#### CAlert attributes
| Attribute | Element | Type | Meaning |
|---|---|---|---|
| `data-intent` | Root | `"info" | "success" | "warn" | "error"` | Mirrors effective visual intent. |
| `data-variant` | Root | `"soft" | "solid" | "outline"` | Mirrors effective visual emphasis. |
| `data-size` | Root | `"sm" | "md" | "lg"` | Mirrors effective geometry. |
| `data-announce` | Root | `"off" | "polite" | "assertive"` | Mirrors effective announcement configuration. |
| `data-icon` | Root | `present | absent` | Present while the decorative indicator is visible. |
| `role` | Content wrapper | `absent | "status" | "alert"` | Native role derived from effective announcement configuration. |
### Selectors
Selectors for the DOM nodes in the components that you can use for CSS, inspection, and
testing.
#### CAlert selectors
| Selector | Element | Purpose |
|---|---|---|
| `[data-citry-ui-part="alert"]` | Root div | Stable Alert surface and `attrs` destination. |
| `[data-citry-ui-part="indicator"]` | Decorative indicator wrapper | Automatic or fixed registered icon container. |
| `[data-citry-ui-part="content"]` | Content wrapper | Title/message group and announcement-role destination. |
| `[data-citry-ui-part="title"]` | Optional title wrapper | Authored title content. |
| `[data-citry-ui-part="message"]` | Optional message wrapper | Authored default-slot content. |
| `[data-citry-ui-part="actions"]` | Optional action wrapper | Action layout, group naming, and `actions_attrs` destination. |
### Interfaces
Aliases and data shapes referenced above.
#### Input type aliases
| Interface | Definition |
|---|---|
| `CClassValue` | `str | Mapping[str, bool] | Sequence[CClassValue]` |
| `CStyleValue` | `str | Mapping[str, str | int | float | bool | None] | Sequence[CStyleValue]` |
| `CAlertIntent` | `Literal["info", "success", "warn", "error"]` |
| `CAlertVariant` | `Literal["soft", "solid", "outline"]` |
| `CAlertSize` | `Literal["sm", "md", "lg"]` |
| `CAlertAnnounce` | `Literal["off", "polite", "assertive"]` |
| `CIconName` | `Literal["arrow-down", "arrow-left", "arrow-right", "arrow-up", "calendar", "check", "chevron-down", "chevron-left", "chevron-right", "chevron-up", "circle-check", "circle-help", "circle-info", "circle-x", "clock", "copy", "download", "edit", "external-link", "eye", "eye-off", "file", "folder", "heart", "home", "leaf", "link", "lock", "mail", "menu", "minus", "more-horizontal", "more-vertical", "plus", "refresh-cw", "search", "settings", "star", "trash", "triangle-alert", "unlock", "upload", "user", "x", "back", "forward", "prev", "next", "close", "clear", "success", "info", "warn", "danger", "expand", "collapse", "dropdown"]` |
#### `CAlertTitleSlotData`
Empty dataclass: `{}`.
#### `CAlertDefaultSlotData`
Empty dataclass: `{}`.
#### `CAlertActionsSlotData`
Empty dataclass: `{}`.
### Translation keys
-
---
# Progress
Source: https://citry.dev/ui-library/components/progress/
# Progress
Use `CProgress` for completion of an ongoing task. It renders the native
`progress` element, so determinate values, unknown duration, direction, and
assistive-technology semantics stay browser-owned.
## Progress at a glance
### Progress at a glance
[Open the rendered preview](/ui-library/components/progress/_previews/at-a-glance/)
````citry
import citry_ui
from citry import Component, citry
citry.register_library(citry_ui)
class ProgressAtAGlance(Component):
template = """
Research dive 08
Mapping the reef shelf
68%
Sonar pass 17 of 25 · 42 minutes remaining
"""
css = """
:where(.progress-glance) {
display: grid;
gap: 0.75rem;
max-inline-size: 38rem;
padding: 1.25rem;
border: 1px solid light-dark(#9fc5d4, #406572);
border-radius: 0.85rem;
background: light-dark(#f0fbff, #11252c);
color: CanvasText;
font-family: ui-sans-serif, system-ui, sans-serif;
}
:where(.progress-glance h2, .progress-glance p) {
margin: 0;
}
:where(.progress-glance > p, .progress-glance [data-citry-ui-part="row"] p) {
color: light-dark(#416a78, #a7cbd7);
font-size: 0.78rem;
}
"""
preview = ProgressAtAGlance()
preview # noqa: B018
````
## Show known completion
Pass a finite `value` from zero through `max`. The default maximum is 100.
### Compare determinate values
[Open the rendered preview](/ui-library/components/progress/_previews/determinate/)
````citry
import citry_ui
from citry import Component, citry
citry.register_library(citry_ui)
class DeterminateProgress(Component):
template = """
Preparing vessel 15%
Descending 50%
Survey complete 100%
"""
css = """
:where(.progress-values) {
max-inline-size: 34rem;
color: CanvasText;
font-family: ui-sans-serif, system-ui, sans-serif;
}
:where(.progress-values > div) {
display: grid;
gap: 0.4rem;
}
"""
preview = DeterminateProgress()
preview # noqa: B018
````
```citry-html
```
## Show unknown duration
Omit `value`, or pass `None`, while work is active but its remaining duration
is unknown. This removes the native value attribute.
### Show indeterminate work
[Open the rendered preview](/ui-library/components/progress/_previews/indeterminate/)
````citry
import citry_ui
from citry import Component, citry
citry.register_library(citry_ui)
class IndeterminateProgress(Component):
template = """
Contacting the deep-sea relay
The operation is active, but its remaining duration is unknown.
"""
css = """
:where(.progress-unknown) {
display: grid;
gap: 0.75rem;
max-inline-size: 32rem;
color: CanvasText;
font-family: ui-sans-serif, system-ui, sans-serif;
}
:where(.progress-unknown h2, .progress-unknown p) {
margin: 0;
}
:where(.progress-unknown p) {
color: GrayText;
font-size: 0.8rem;
}
"""
preview = IndeterminateProgress()
preview # noqa: B018
````
Reduced-motion preferences replace continuous motion with a static patterned
track.
## Use custom units
Set a positive `max` and supply `value_text` when the value is better explained
as items, bytes, stages, or another unit.
### Use a custom range and value text
[Open the rendered preview](/ui-library/components/progress/_previews/custom-range/)
````citry
import citry_ui
from citry import Component, citry
citry.register_library(citry_ui)
class CustomRangeProgress(Component):
template = """
Sample crates cataloged 6 / 10
"""
css = """
:where(.progress-range) {
display: grid;
gap: 0.75rem;
max-inline-size: 32rem;
color: CanvasText;
font-family: ui-sans-serif, system-ui, sans-serif;
}
:where(.progress-range h2) {
margin: 0;
font-size: 0.95rem;
}
"""
preview = CustomRangeProgress()
preview # noqa: B018
````
## Choose a palette
Intent changes the range color. Keep the task label and surrounding text clear
without color.
### Compare Progress intents
[Open the rendered preview](/ui-library/components/progress/_previews/intents/)
````citry
import citry_ui
from citry import Component, citry
citry.register_library(citry_ui)
class ProgressIntents(Component):
template = """
{{ item[1] }}
"""
class Kwargs:
pass
class Slots:
pass
def template_data(
self,
kwargs: Kwargs, # noqa: ARG002
slots: Slots, # noqa: ARG002
) -> dict[str, object]:
return {
"items": (
("neutral", "Equipment check"),
("primary", "Survey pass"),
("success", "Samples secured"),
("warn", "Current increasing"),
("danger", "Pressure limit"),
)
}
css = """
:where(.progress-intents) {
max-inline-size: 32rem;
color: CanvasText;
font-family: ui-sans-serif, system-ui, sans-serif;
}
:where(.progress-intents > div) {
display: grid;
gap: 0.3rem;
}
:where(.progress-intents span) {
font-size: 0.8rem;
}
"""
preview = ProgressIntents()
preview # noqa: B018
````
## Choose thickness and shape
Sizes set track thickness. Shape selects square, rounded, or pill geometry.
### Compare Progress sizes and shapes
[Open the rendered preview](/ui-library/components/progress/_previews/sizes-and-shapes/)
````citry
import citry_ui
from citry import Component, citry
citry.register_library(citry_ui)
class ProgressSizesAndShapes(Component):
template = """
"""
css = """
:where(.progress-sizes) {
max-inline-size: 34rem;
color: CanvasText;
}
"""
preview = ProgressSizesAndShapes()
preview # noqa: B018
````
## Control progress in the browser
Client inputs are passed through `$c-props="{...}"`. A number controls
determinate completion; `null` switches to indeterminate; omission returns to
the server fallback.
### Control Progress in the browser
[Open the rendered preview](/ui-library/components/progress/_previews/controlled/)
````citry
import citry_ui
from citry import Component, citry
citry.register_library(citry_ui)
class ControlledProgress(Component):
template = """
"""
css = """
:where(.progress-controlled) {
display: grid;
gap: 0.85rem;
max-inline-size: 34rem;
color: CanvasText;
font-family: ui-sans-serif, system-ui, sans-serif;
}
:where(.progress-controlled h2) {
margin: 0;
font-size: 1rem;
}
:where(.progress-controlled label) {
display: grid;
gap: 0.35rem;
font-size: 0.8rem;
}
:where(.progress-controlled input) {
inline-size: 100%;
}
"""
preview = ControlledProgress()
preview # noqa: B018
````
## Describe a busy region
When Progress describes another region, the application owns `aria-busy` on
that region and connects it to Progress. Clear busy state when the work
finishes.
### Connect Progress to a busy region
[Open the rendered preview](/ui-library/components/progress/_previews/busy-region/)
````citry
import citry_ui
from citry import Component, citry
citry.register_library(citry_ui)
class BusyRegionProgress(Component):
template = """
Reconstructing the reef map
Existing survey results remain visible while the new contour layer loads.
"""
css = """
:where(.progress-busy) {
display: grid;
gap: 0.75rem;
max-inline-size: 34rem;
padding: 1rem;
border: 1px solid light-dark(#b5d0d9, #436571);
border-radius: 0.75rem;
color: CanvasText;
font-family: ui-sans-serif, system-ui, sans-serif;
}
:where(.progress-busy h2, .progress-busy p) {
margin: 0;
}
"""
preview = BusyRegionProgress()
preview # noqa: B018
````
## Customize Progress
Override public track, range, height, and radius variables on an ancestor or
one native Progress root.
### Customize Progress with public CSS
[Open the rendered preview](/ui-library/components/progress/_previews/customization/)
````citry
import citry_ui
from citry import Component, citry
citry.register_library(citry_ui)
class ProgressCustomization(Component):
template = """
"""
css = """
:where(.progress-themes > div) {
padding: 1.25rem;
border-radius: 0.75rem;
}
:where(.progress-themes__coral) {
--cui-progress-track-color: #f8ddd6;
--cui-progress-range-color: #b9382f;
--cui-progress-height: 0.75rem;
background: #fff6f2;
}
:where(.progress-themes__abyss) {
color-scheme: dark;
--cui-progress-track-color: #1f3b48;
--cui-progress-range-color: #63d4e8;
--cui-progress-height: 0.75rem;
background: #0b1b24;
}
"""
preview = ProgressCustomization()
preview # noqa: B018
````
## Choose the right indicator
Progress represents task completion. Use `CSpinner` for a compact unknown wait
without a linear track, and native `meter` for a scalar measurement that is not
task completion.
Progress has no focus, keyboard behavior, form value, live announcement, or
automatic busy-region mutation.
## API reference
### Inputs
#### CProgress server inputs
Server inputs are passed in a template through ` ` or in Python through
`CProgress(...)`.
| Input | Type | Default | Effect |
|---|---|---|---|
| `label` | `str` | required | Sets the required nonempty native accessible name and fallback text. |
| `value` | `float | int | None` | `None` | Sets determinate completion from zero through max; None omits the native value attribute for indeterminate progress. |
| `max` | `float | int` | `100` | Sets the positive native task maximum. |
| `value_text` | `str | None` | `None` | Sets optional `aria-valuetext` when units are not naturally understood as a percentage. |
| `intent` | `"neutral" | "primary" | "success" | "warn" | "danger"` ([`CProgressIntent`](#progress-interface-input-type-aliases-cprogress-intent)) | `"primary"` | Selects the visual range palette; surrounding text still carries meaning. |
| `size` | `"sm" | "md" | "lg"` ([`CProgressSize`](#progress-interface-input-type-aliases-cprogress-size)) | `"md"` | Sets track thickness. |
| `shape` | `"square" | "rounded" | "pill"` ([`CProgressShape`](#progress-interface-input-type-aliases-cprogress-shape)) | `"rounded"` | Sets native track and range radius. |
| `class_` | `str | Mapping[str, bool] | Sequence[CClassValue] | None` ([`CClassValue`](#progress-interface-input-type-aliases-class-value)) | `None` | Adds native root classes and merges them with `attrs`. |
| `style` | `str | Mapping[str, str | int | float | bool | None] | Sequence[CStyleValue] | None` ([`CStyleValue`](#progress-interface-input-type-aliases-style-value)) | `None` | Adds native root inline styles and merges them with `attrs`. |
| `attrs` | `Mapping[str, object] | None` | `None` | Adds copied trusted nonconflicting native, ARIA relationship, data, and targeted Alpine attributes to the native progress root. |
#### CProgress client inputs
Client inputs are passed in the browser through the `$c-props="{ ... }"` attribute on
` `.
| Input | Type | Omitted behavior | Effect |
|---|---|---|---|
| `value` | `number | null` | Uses the server fallback. | Controls native determinate value; null removes the attribute for indeterminate state; omission returns to server fallback. |
| `label` | `string` | Uses the server fallback. | Controls the nonempty native accessible name; omission returns to server fallback. |
| `valueText` | `string | null` | Uses the server fallback. | Controls `aria-valuetext`; null removes it; omission returns to server fallback. |
| `intent` | `"neutral" | "primary" | "success" | "warn" | "danger"` | Uses the server fallback. | Controls the public visual palette reflection. |
| `size` | `"sm" | "md" | "lg"` | Uses the server fallback. | Controls the public thickness reflection. |
| `shape` | `"square" | "rounded" | "pill"` | Uses the server fallback. | Controls the public radius reflection. |
### Slots
-
### Events
-
### Methods
-
### CSS
CSS variables to theme the components. Set them on an ancestor or the component itself.
#### CProgress CSS variables
Apply these variables to `CProgress` or one of its ancestors.
| Variable | Type | Purpose | Default |
|---|---|---|---|
| `--cui-progress-track-color` | `color` | Unfilled native track. | `Scheme-aware neutral color.` |
| `--cui-progress-range-color` | `color` | Completed range and indeterminate accent. | `Intent-derived color.` |
| `--cui-progress-height` | `length` | Native track thickness. | `Size-derived length.` |
| `--cui-progress-radius` | `length` | Native track and range radius. | `Shape-derived length.` |
### Attributes
HTML attributes defined on the components that you can refer to for CSS, inspection, and
testing. Read-only.
#### CProgress attributes
| Attribute | Element | Type | Meaning |
|---|---|---|---|
| `value` | Native root | `number or absent` | Present only for determinate progress and controlled by the effective value. |
| `max` | Native root | `positive number` | Reflects the server-owned task maximum. |
| `aria-label` | Native root | `nonempty string` | Carries the required accessible task name. |
| `aria-valuetext` | Native root | `string or absent` | Carries optional application-authored value phrasing. |
| `data-state` | Native root | `"determinate" | "indeterminate"` | Reflects whether the native value attribute is present. |
| `data-intent` | Native root | `"neutral" | "primary" | "success" | "warn" | "danger"` | Reflects the effective visual palette. |
| `data-size` | Native root | `"sm" | "md" | "lg"` | Reflects effective thickness. |
| `data-shape` | Native root | `"square" | "rounded" | "pill"` | Reflects effective radius. |
### Selectors
Selectors for the DOM nodes in the components that you can use for CSS, inspection, and
testing.
#### CProgress selectors
| Selector | Element | Purpose |
|---|---|---|
| `[data-citry-ui-part="progress"]` | Native progress root | Stable public root and `attrs` destination. |
### Interfaces
Aliases and data shapes referenced above.
#### Input type aliases
| Interface | Definition |
|---|---|
| `CClassValue` | `str | Mapping[str, bool] | Sequence[CClassValue]` |
| `CStyleValue` | `str | Mapping[str, str | int | float | bool | None] | Sequence[CStyleValue]` |
| `CProgressIntent` | `Literal["neutral", "primary", "success", "warn", "danger"]` |
| `CProgressSize` | `Literal["sm", "md", "lg"]` |
| `CProgressShape` | `Literal["square", "rounded", "pill"]` |
### Translation keys
Catalog keys used by this family. An explicit component input or slot listed in Override
takes precedence over the catalog for that instance.
#### CProgress translation keys
| Key | Purpose | Variables | Override | Browser updates |
|---|---|---|---|---|
| `citry-ui-progress-value-text` | Provides readable fallback text for determinate progress. | `label: str; value: str; max: str` | None | `i18n.bind()` tracks locale and reactive value changes. |
---
# Skeleton
Source: https://citry.dev/ui-library/components/skeleton/
# Skeleton
Use `CSkeleton` to hold a known layout while its data loads. Compose explicit
primitives instead of encoding a page shape in a preset string.
## Skeleton at a glance
### Skeleton at a glance
[Open the rendered preview](/ui-library/components/skeleton/_previews/at-a-glance/)
````citry
import citry_ui
from citry import Component, citry
citry.register_library(citry_ui)
class SkeletonGlance(Component):
template = """
"""
css = """
:where(.skeleton-glance) {
display: grid;
max-inline-size: 24rem;
gap: 1rem;
padding: 1rem;
border: 1px solid light-dark(#b8cbb9, #425947);
border-radius: 0.9rem;
}
"""
preview = SkeletonGlance()
preview # noqa: B018
````
## Choose a primitive
Rectangles hold media and panels, circles hold avatars and icons, and text
lines track typography.
### Compare Skeleton primitives
[Open the rendered preview](/ui-library/components/skeleton/_previews/primitives/)
````citry
import citry_ui
from citry import Component, citry
citry.register_library(citry_ui)
class SkeletonPrimitives(Component):
template = """
"""
css = """
:where(.skeleton-primitives) {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 1rem;
}
"""
preview = SkeletonPrimitives()
preview # noqa: B018
````
## Shape text
`lines` produces compact paragraph geometry. Set the final line width to make
the placeholder resemble real prose.
### Compose text lines
[Open the rendered preview](/ui-library/components/skeleton/_previews/text-lines/)
````citry
import citry_ui
from citry import Component, citry
citry.register_library(citry_ui)
class SkeletonText(Component):
template = """
"""
css = """
:where(.skeleton-text) {
display: grid;
max-inline-size: 30rem;
gap: 1rem;
}
"""
preview = SkeletonText()
preview # noqa: B018
````
## Compose real layouts
Build familiar patterns with `CCol`, `CRow`, and ordinary CSS. The visible
structure stays inspectable and responsive.
### Compose a field-note card
[Open the rendered preview](/ui-library/components/skeleton/_previews/field-note-card/)
````citry
import citry_ui
from citry import Component, citry
citry.register_library(citry_ui)
class SkeletonCard(Component):
template = """
"""
preview = SkeletonCard()
preview # noqa: B018
````
### Compose a specimen list
[Open the rendered preview](/ui-library/components/skeleton/_previews/specimen-list/)
````citry
import citry_ui
from citry import Component, citry
citry.register_library(citry_ui)
class SkeletonList(Component):
class Kwargs:
pass
class Slots:
pass
template = """
"""
def template_data(
self,
kwargs: Kwargs, # noqa: ARG002
slots: Slots, # noqa: ARG002
) -> dict[str, object]:
return {"items": (0, 1, 2)}
css = """
:where(.skeleton-list) {
display: grid;
max-inline-size: 30rem;
gap: 1rem;
}
:where(.skeleton-list [data-citry-ui-part="row"] > :nth-child(2)) {
flex: 1 1 auto;
}
"""
preview = SkeletonList()
preview # noqa: B018
````
## Choose motion
Pulse is the default. Wave provides stronger progress motion, while none makes
a static wireframe. Reduced-motion preferences disable both animations.
### Compare motion treatments
[Open the rendered preview](/ui-library/components/skeleton/_previews/motion/)
````citry
import citry_ui
from citry import Component, citry
citry.register_library(citry_ui)
class SkeletonMotion(Component):
class Kwargs:
pass
class Slots:
pass
template = """
"""
def template_data(
self,
kwargs: Kwargs, # noqa: ARG002
slots: Slots, # noqa: ARG002
) -> dict[str, object]:
return {"motions": ("pulse", "wave", "none")}
css = """
:where(.skeleton-motion) {
display: grid;
gap: 0.75rem;
}
:where(.skeleton-motion > div) {
display: grid;
grid-template-columns: 4rem 1fr;
align-items: center;
gap: 0.75rem;
font: 0.75rem ui-sans-serif, system-ui, sans-serif;
}
"""
preview = SkeletonMotion()
preview # noqa: B018
````
## Customize Skeleton
Public variables control dimensions, color, radius, spacing, and timing.
### Customize Skeleton with public CSS
[Open the rendered preview](/ui-library/components/skeleton/_previews/customization/)
````citry
import citry_ui
from citry import Component, citry
citry.register_library(citry_ui)
class SkeletonCustomization(Component):
template = """
"""
css = """
:where(.skeleton-lichen) {
--cui-skeleton-background: light-dark(#c9dfc8, #36513c);
--cui-skeleton-highlight: light-dark(rgb(255 255 255 / 70%), rgb(190 239 200 / 28%));
--cui-skeleton-radius: 1rem;
display: grid;
max-inline-size: 24rem;
gap: 1rem;
}
"""
preview = SkeletonCustomization()
preview # noqa: B018
````
## Accessibility and loading ownership
Skeletons are decorative and hidden from assistive technology. Put
`aria-busy="true"` and a useful accessible name on the region whose content is
loading. That region, not Skeleton, owns async state and announcements.
## API reference
### Inputs
#### CSkeleton server inputs
Server inputs are passed in a template through ` ` or in Python through
`CSkeleton(...)`.
| Input | Type | Default | Effect |
|---|---|---|---|
| `kind` | `"rect" | "text" | "circle"` ([`CSkeletonKind`](#skeleton-interface-kind)) | `"rect"` | Selects primitive geometry. |
| `lines` | `int (1..100)` | `1` | Renders one or more text lines; values above one require text kind. |
| `animation` | `"pulse" | "wave" | "none"` ([`CSkeletonAnimation`](#skeleton-interface-animation)) | `"pulse"` | Selects CSS-only motion. Reduced-motion always disables it. |
| `width` | `str | None` | `None` | Sets the root width to one CSS length or percentage. |
| `height` | `str | None` | `None` | Sets the primitive or line height to one CSS length or percentage. |
| `last_line_width` | `str` | `"70%"` | Sets the final line width when multiple text lines render. |
| `class_` | `str | Mapping[str, bool] | Sequence[CClassValue] | None` ([`CClassValue`](#skeleton-interface-class-value)) | `None` | Adds root classes and merges them with `attrs`. |
| `style` | `str | Mapping[str, str | int | float | bool | None] | Sequence[CStyleValue] | None` ([`CStyleValue`](#skeleton-interface-style-value)) | `None` | Adds root inline styles before direct dimension inputs. |
| `attrs` | `Mapping[str, object] | None` | `None` | Adds copied trusted root attributes without replacing decorative semantics, children, reflections, focus, or Citry runtime fields. |
### Slots
-
### Events
-
### Methods
-
### CSS
CSS variables to theme the components. Set them on an ancestor or the component itself.
#### CSkeleton CSS variables
Apply these variables to `CSkeleton` or one of its ancestors.
| Variable | Type | Purpose | Default |
|---|---|---|---|
| `--cui-skeleton-width` | `length-or-percentage` | Root or line-group width. | `Kind-derived 100% or 3rem.` |
| `--cui-skeleton-height` | `length-or-percentage` | Root or line height. | `Kind-derived 6rem, 0.75em, or 3rem.` |
| `--cui-skeleton-radius` | `length` | Primitive corners. | `Kind-derived 0.5rem, 999px, or 50%.` |
| `--cui-skeleton-background` | `color` | Resting placeholder surface. | `Scheme-derived neutral.` |
| `--cui-skeleton-highlight` | `color` | Wave highlight. | `Translucent white.` |
| `--cui-skeleton-gap` | `length` | Text line gap. | `0.5em` |
| `--cui-skeleton-duration` | `time` | Pulse or wave cycle. | `1.5s` |
| `--cui-skeleton-last-line-width` | `length-or-percentage` | Final text-line width. | `Input-derived 70%.` |
### Attributes
HTML attributes defined on the components that you can refer to for CSS, inspection, and
testing. Read-only.
#### CSkeleton attributes
| Attribute | Element | Type | Meaning |
|---|---|---|---|
| `data-kind` | Root | `"rect" | "text" | "circle"` | Reflects primitive geometry. |
| `data-animation` | Root | `"pulse" | "wave" | "none"` | Reflects requested motion. |
| `aria-hidden` | Root | `"true"` | Keeps decorative placeholder geometry out of the accessibility tree. |
### Selectors
Selectors for the DOM nodes in the components that you can use for CSS, inspection, and
testing.
#### CSkeleton selectors
| Selector | Element | Purpose |
|---|---|---|
| `[data-citry-ui-part="skeleton"]` | Root span | Stable primitive and attrs destination. |
| `[data-citry-ui-part="line"]` | Text line span | Stable direct child in text mode. |
### Interfaces
Aliases and data shapes referenced above.
#### Input type aliases
| Interface | Definition |
|---|---|
| `CClassValue` | `str | Mapping[str, bool] | Sequence[CClassValue]` |
| `CStyleValue` | `str | Mapping[str, str | int | float | bool | None] | Sequence[CStyleValue]` |
| `CSkeletonKind` | `Literal["rect", "text", "circle"]` |
| `CSkeletonAnimation` | `Literal["pulse", "wave", "none"]` |
### Translation keys
-
---
# Spinner
Source: https://citry.dev/ui-library/components/spinner/
# Spinner
Use `CSpinner` for compact activity whose duration is unknown. It renders one
indeterminate `progressbar`, works before JavaScript loads, and always requires
an accessible task label.
## Spinner at a glance
### Spinner at a glance
[Open the rendered preview](/ui-library/components/spinner/_previews/at-a-glance/)
````citry
import citry_ui
from citry import Component, citry
citry.register_library(citry_ui)
class SpinnerAtAGlance(Component):
template = """
✦ · ✧ · ✦
Calibrating the camera Reading dark frames from the observatory sensor.
"""
css = """
:where(.spinner-glance) {
display: grid;
gap: 1rem;
max-inline-size: 34rem;
padding: 1.25rem;
border: 1px solid light-dark(#b8b8dd, #4b4a78);
border-radius: 0.9rem;
background: light-dark(#f7f6ff, #17172a);
color: CanvasText;
font-family: ui-sans-serif, system-ui, sans-serif;
}
:where(.spinner-glance__sky) {
color: light-dark(#5b4bb7, #c4b5fd);
font-size: 1.3rem;
letter-spacing: 0.6rem;
text-align: center;
}
:where(.spinner-glance h2, .spinner-glance p) {
margin: 0;
}
:where(.spinner-glance p) {
margin-block-start: 0.25rem;
color: light-dark(#55546f, #c6c4de);
font-size: 0.8rem;
}
"""
preview = SpinnerAtAGlance()
preview # noqa: B018
````
## Show active work
Pass a concise label that identifies the active task. Spinner does not display
the label, so pair it with visible text when users need the same context.
### Show basic Spinners
[Open the rendered preview](/ui-library/components/spinner/_previews/basic/)
````citry
import citry_ui
from citry import Component, citry
citry.register_library(citry_ui)
class BasicSpinners(Component):
template = """
"""
css = """
:where(.spinner-basic) {
padding: 1.25rem;
color: CanvasText;
}
"""
preview = BasicSpinners()
preview # noqa: B018
````
```citry-html
```
## Choose a palette
Intent changes the ring color. Keep status meaning in surrounding text rather
than color alone.
### Compare Spinner intents
[Open the rendered preview](/ui-library/components/spinner/_previews/intents/)
````citry
import citry_ui
from citry import Component, citry
citry.register_library(citry_ui)
class SpinnerIntents(Component):
class Kwargs:
pass
class Slots:
pass
template = """
{{ intent }}
"""
def template_data(
self,
kwargs: Kwargs, # noqa: ARG002
slots: Slots, # noqa: ARG002
) -> dict[str, object]:
return {"intents": ("neutral", "primary", "success", "warn", "danger")}
css = """
:where(.spinner-intents) {
color: CanvasText;
font-family: ui-sans-serif, system-ui, sans-serif;
}
:where(.spinner-intents span) {
font-size: 0.72rem;
}
"""
preview = SpinnerIntents()
preview # noqa: B018
````
## Choose a size
Use `sm`, `md`, or `lg`. Public CSS variables can set a one-off diameter or
thickness.
### Compare Spinner sizes
[Open the rendered preview](/ui-library/components/spinner/_previews/sizes/)
````citry
import citry_ui
from citry import Component, citry
citry.register_library(citry_ui)
class SpinnerSizes(Component):
class Kwargs:
pass
class Slots:
pass
template = """
{{ size }}
"""
def template_data(
self,
kwargs: Kwargs, # noqa: ARG002
slots: Slots, # noqa: ARG002
) -> dict[str, object]:
return {"sizes": ("sm", "md", "lg")}
css = """
:where(.spinner-sizes) {
min-block-size: 4rem;
color: CanvasText;
font-family: ui-sans-serif, system-ui, sans-serif;
}
:where(.spinner-sizes span) {
font-size: 0.72rem;
}
"""
preview = SpinnerSizes()
preview # noqa: B018
````
## Pair Spinner with text
Spinner is inline-sized and works beside concise status text. It never adds a
focus stop or changes surrounding controls.
### Compose inline activity
[Open the rendered preview](/ui-library/components/spinner/_previews/inline/)
````citry
import citry_ui
from citry import Component, citry
citry.register_library(citry_ui)
class InlineSpinner(Component):
template = """
Indexing nebula spectra
The rest of the observing log remains readable while the index catches up.
"""
css = """
:where(.spinner-inline) {
max-inline-size: 34rem;
color: CanvasText;
font-family: ui-sans-serif, system-ui, sans-serif;
}
:where(.spinner-inline p) {
margin-block-end: 0;
color: light-dark(#57566f, #c8c6df);
font-size: 0.8rem;
}
"""
preview = InlineSpinner()
preview # noqa: B018
````
## Control presentation in the browser
Client inputs are passed through `$c-props="{...}"`. They can update `label`,
`intent`, and `size`; omission returns to the server fallback.
### Control Spinner in the browser
[Open the rendered preview](/ui-library/components/spinner/_previews/controlled/)
````citry
import citry_ui
from citry import Component, citry
citry.register_library(citry_ui)
class ControlledSpinner(Component):
template = """
Refreshing orbital catalog
Intent
primary success
warn danger
Size
sm md lg
"""
css = """
:where(.spinner-controlled) {
display: grid;
gap: 1rem;
color: CanvasText;
font-family: ui-sans-serif, system-ui, sans-serif;
}
:where(.spinner-controlled label) {
display: grid;
gap: 0.3rem;
font-size: 0.75rem;
}
"""
preview = ControlledSpinner()
preview # noqa: B018
````
## Describe a busy region
The region owner sets `aria-busy`, controls Spinner presence, and clears busy
state when work completes. Spinner does not mutate another element.
### Connect Spinner to a busy region
[Open the rendered preview](/ui-library/components/spinner/_previews/busy-region/)
````citry
import citry_ui
from citry import Component, citry
citry.register_library(citry_ui)
class SpinnerBusyRegion(Component):
template = """
Updating the star chart
✦ · ✧
· ✦ ·
"""
css = """
:where(.spinner-busy) {
display: grid;
gap: 1rem;
max-inline-size: 30rem;
color: CanvasText;
font-family: ui-sans-serif, system-ui, sans-serif;
}
:where(.spinner-busy__chart) {
min-block-size: 5rem;
padding: 1rem;
border-radius: 0.7rem;
background: light-dark(#ecebff, #1d1d35);
color: light-dark(#5148a0, #c4b5fd);
letter-spacing: 0.7rem;
line-height: 2;
}
"""
preview = SpinnerBusyRegion()
preview # noqa: B018
````
## Avoid flashes for brief work
Delay Spinner in application state when a task normally finishes immediately.
The application also owns any minimum-visible duration.
### Delay brief activity feedback
[Open the rendered preview](/ui-library/components/spinner/_previews/delayed/)
````citry
import citry_ui
from citry import Component, citry
citry.register_library(citry_ui)
class DelayedSpinner(Component):
template = """
Toggle long-running observation
Waiting for the long exposure
Real applications show this only after their chosen delay.
"""
css = """
:where(.spinner-delayed) {
display: grid;
gap: 0.75rem;
color: CanvasText;
font-family: ui-sans-serif, system-ui, sans-serif;
}
:where(.spinner-delayed__status) {
display: flex;
align-items: center;
gap: 0.5rem;
}
:where(.spinner-delayed p) {
margin: 0;
color: light-dark(#57566f, #c8c6df);
font-size: 0.78rem;
}
"""
preview = DelayedSpinner()
preview # noqa: B018
````
## Customize Spinner
Override public color, track, diameter, thickness, and duration variables on an
ancestor or one Spinner root.
### Customize Spinner with public CSS
[Open the rendered preview](/ui-library/components/spinner/_previews/customization/)
````citry
import citry_ui
from citry import Component, citry
citry.register_library(citry_ui)
class SpinnerCustomization(Component):
template = """
"""
css = """
:where(.spinner-custom > div) {
display: grid;
place-items: center;
min-inline-size: 5rem;
min-block-size: 5rem;
border-radius: 0.75rem;
background: light-dark(#f5f4ff, #17172a);
}
:where(.spinner-custom__violet) {
--cui-spinner-color: #7c3aed;
--cui-spinner-track-color: #ddd6fe;
--cui-spinner-size: 2rem;
}
:where(.spinner-custom__solar) {
--cui-spinner-color: #c2410c;
--cui-spinner-track-color: #fed7aa;
--cui-spinner-thickness: 0.24rem;
}
:where(.spinner-custom__ice) {
--cui-spinner-color: #0891b2;
--cui-spinner-track-color: #a5f3fc;
--cui-spinner-duration: 1.2s;
}
"""
preview = SpinnerCustomization()
preview # noqa: B018
````
## Choose the right indicator
Use `CProgress` when completion has a meaningful linear track or known value.
Use `CButton(loading=True)` for a Button-owned pending action. Spinner does not
own overlays, live announcements, task timing, or determinate values.
## API reference
### Inputs
#### CSpinner server inputs
Server inputs are passed in a template through ` ` or in Python through
`CSpinner(...)`.
| Input | Type | Default | Effect |
|---|---|---|---|
| `label` | `str` | required | Sets the required nonempty accessible task name. |
| `intent` | `"neutral" | "primary" | "success" | "warn" | "danger"` ([`CSpinnerIntent`](#spinner-interface-input-type-aliases-cspinner-intent)) | `"primary"` | Selects the visual ring palette; surrounding text still carries meaning. |
| `size` | `"sm" | "md" | "lg"` ([`CSpinnerSize`](#spinner-interface-input-type-aliases-cspinner-size)) | `"md"` | Sets ring diameter and default thickness. |
| `class_` | `str | Mapping[str, bool] | Sequence[CClassValue] | None` ([`CClassValue`](#spinner-interface-input-type-aliases-class-value)) | `None` | Adds root classes and merges them with `attrs`. |
| `style` | `str | Mapping[str, str | int | float | bool | None] | Sequence[CStyleValue] | None` ([`CStyleValue`](#spinner-interface-input-type-aliases-style-value)) | `None` | Adds root inline styles and merges them with `attrs`. |
| `attrs` | `Mapping[str, object] | None` | `None` | Adds copied trusted nonconflicting metadata, description relationships, visibility, and targeted Alpine attributes to the Spinner root. |
#### CSpinner client inputs
Client inputs are passed in the browser through the `$c-props="{ ... }"` attribute on
` `.
| Input | Type | Omitted behavior | Effect |
|---|---|---|---|
| `label` | `string` | Uses the server fallback. | Controls the nonempty accessible task name; omission returns to server fallback. |
| `intent` | `"neutral" | "primary" | "success" | "warn" | "danger"` | Uses the server fallback. | Controls the public visual palette reflection. |
| `size` | `"sm" | "md" | "lg"` | Uses the server fallback. | Controls the public size reflection. |
### Slots
-
### Events
-
### Methods
-
### CSS
CSS variables to theme the components. Set them on an ancestor or the component itself.
#### CSpinner CSS variables
Apply these variables to `CSpinner` or one of its ancestors.
| Variable | Type | Purpose | Default |
|---|---|---|---|
| `--cui-spinner-color` | `color` | Active ring arc. | `Intent-derived color.` |
| `--cui-spinner-track-color` | `color` | Quiet remainder of the ring. | `Current color mixed with transparency.` |
| `--cui-spinner-size` | `length` | Ring diameter. | `Size-derived length.` |
| `--cui-spinner-thickness` | `length` | Ring border width. | `Size-derived length.` |
| `--cui-spinner-duration` | `time` | One rotation duration when motion is allowed. | `0.75s.` |
### Attributes
HTML attributes defined on the components that you can refer to for CSS, inspection, and
testing. Read-only.
#### CSpinner attributes
| Attribute | Element | Type | Meaning |
|---|---|---|---|
| `role` | Root | `"progressbar"` | Exposes an indeterminate progress indicator. |
| `aria-label` | Root | `nonempty string` | Carries the required accessible task name. |
| `data-intent` | Root | `"neutral" | "primary" | "success" | "warn" | "danger"` | Reflects the effective visual palette. |
| `data-size` | Root | `"sm" | "md" | "lg"` | Reflects effective diameter and default thickness. |
### Selectors
Selectors for the DOM nodes in the components that you can use for CSS, inspection, and
testing.
#### CSpinner selectors
| Selector | Element | Purpose |
|---|---|---|
| `[data-citry-ui-part="spinner"]` | Spinner root | Stable public root and `attrs` destination. |
### Interfaces
Aliases and data shapes referenced above.
#### Input type aliases
| Interface | Definition |
|---|---|
| `CClassValue` | `str | Mapping[str, bool] | Sequence[CClassValue]` |
| `CStyleValue` | `str | Mapping[str, str | int | float | bool | None] | Sequence[CStyleValue]` |
| `CSpinnerIntent` | `Literal["neutral", "primary", "success", "warn", "danger"]` |
| `CSpinnerSize` | `Literal["sm", "md", "lg"]` |
### Translation keys
-
---
# Toast
Source: https://citry.dev/ui-library/components/toast/
# Toast
Use `CToastRegion` once near the end of an application root. It owns a
persistent visible queue, polite and assertive announcers, remaining-time
pause, action/dismiss semantics, and F6 focus access. Arrival never steals
focus.
## Toast at a glance
Intent controls presentation; priority independently controls announcement
urgency.
### Toast at a glance
[Open the rendered preview](/ui-library/components/toast/_previews/at-a-glance/)
````citry
from typing import Any
import citry_ui
from citry import Component, citry
citry.register_library(citry_ui)
class ToastAtAGlance(Component):
template = """
These initial messages demonstrate presentation intent separately from urgency.
"""
def template_data(self, kwargs: Any, slots: Any) -> dict[str, object]: # noqa: ARG002
return {
"items": tuple(
citry_ui.CToastMessage(id=intent, title=title, intent=intent)
for intent, title in (
("neutral", "Draft retained"),
("info", "Sync started"),
("success", "Field note saved"),
("warn", "Connection is slow"),
("error", "Upload failed"),
)
)
}
css = ":where(.toast-sampler) { min-block-size:20rem; padding:1rem; }"
preview = ToastAtAGlance()
preview # noqa: B018
````
## Drive a reactive queue
Pass an Array of plain client message records. A stable `id` is queue identity.
Remove IDs in `onDismiss` so expired or dismissed messages can later begin a
fresh episode.
### Add application notifications
[Open the rendered preview](/ui-library/components/toast/_previews/reactive-queue/)
````citry
import citry_ui
from citry import Component, citry
citry.register_library(citry_ui)
class ReactiveToastQueue(Component):
template = """
Add notification
notices = notices.filter(item => item.id !== id),
}" />
"""
css = ":where(.toast-example) { min-block-size:16rem; padding:1rem; }"
preview = ReactiveToastQueue()
preview # noqa: B018
````
```citry-html
notices = notices.filter(item => item.id !== id),
}"
/>
```
## Replace and deduplicate by ID
A retained ID updates in place. A material update restarts its lifetime and
announces the replacement once; a byte-equivalent snapshot does neither.
### Replace a message
[Open the rendered preview](/ui-library/components/toast/_previews/replacement/)
````citry
import citry_ui
from citry import Component, citry
citry.register_library(citry_ui)
class ToastReplacement(Component):
template = """
"""
css = ":where(.toast-example) { min-block-size:16rem; padding:1rem; }"
preview = ToastReplacement()
preview # noqa: B018
````
## Pause remaining lifetime
The default lifetime is eight seconds. Set `duration_ms=0` for persistent
messages. Hover, focus within, document visibility, and an unrelated modal
pause remaining time rather than starting a new timeout.
### Pause a timed Toast
[Open the rendered preview](/ui-library/components/toast/_previews/timeout-pause/)
````citry
import citry_ui
from citry import Component, citry
citry.register_library(citry_ui)
class TimedToast(Component):
template = """
Start timed Toast
items = items.filter(item => item.id !== id),
}" />
"""
css = ":where(.toast-example) { min-block-size:16rem; padding:1rem; }"
preview = TimedToast()
preview # noqa: B018
````
## Add one persistent action
`onAction` runs before action-caused dismissal. Set `closeOnAction: false` when
the result should remain visible.
### Act on a notification
[Open the rendered preview](/ui-library/components/toast/_previews/persistent-action/)
````citry
import citry_ui
from citry import Component, citry
citry.register_library(citry_ui)
class PersistentToastAction(Component):
template = """
Show persistent action
result = 'Retry requested',
onDismiss: id => items = items.filter(item => item.id !== id),
}" />
"""
css = ":where(.toast-example) { display:grid; gap:.75rem; min-block-size:16rem; padding:1rem; }"
preview = PersistentToastAction()
preview # noqa: B018
````
## Limit the visible stack
Only the first `limit` unsuppressed messages render, announce, and run timers.
Queued records start when promoted.
### Queue beyond the visible limit
[Open the rendered preview](/ui-library/components/toast/_previews/visible-limit/)
````citry
from typing import Any
import citry_ui
from citry import Component, citry
citry.register_library(citry_ui)
class ToastVisibleLimit(Component):
template = """
Dismiss a visible message to promote the queued third item.
"""
def template_data(self, kwargs: Any, slots: Any) -> dict[str, object]: # noqa: ARG002
return {
"items": tuple(
citry_ui.CToastMessage(id=f"queue-{index}", title=f"Queue item {index}") for index in range(1, 4)
)
}
css = ":where(.toast-example) { min-block-size:16rem; padding:1rem; }"
preview = ToastVisibleLimit()
preview # noqa: B018
````
## Reach notifications with F6
Unmodified F6 moves from the application to the first presented Toast. F6
inside returns to the recorded element. Tab remains ordinary and is never
trapped.
### Use the F6 focus route
[Open the rendered preview](/ui-library/components/toast/_previews/focus-access/)
````citry
from typing import Any
import citry_ui
from citry import Component, citry
citry.register_library(citry_ui)
class ToastFocusAccess(Component):
template = """
Focus this page, then press F6 to enter the notification and F6 again to return.
Focus before F6
"""
def template_data(self, kwargs: Any, slots: Any) -> dict[str, object]: # noqa: ARG002
return {"items": (citry_ui.CToastMessage(id="f6", title="F6 reaches this message"),)}
css = ":where(.toast-example) { min-block-size:16rem; padding:1rem; }"
preview = ToastFocusAccess()
preview # noqa: B018
````
## Pause behind a modal
A global Region becomes hidden, inert, and paused while an unrelated native
modal is open. Use `CAlert` inside the modal for feedback that must be immediate
there.
### Keep modal feedback local
[Open the rendered preview](/ui-library/components/toast/_previews/modal-pause/)
````citry
import citry_ui
from citry import Component, citry
citry.register_library(citry_ui)
class ToastModalPause(Component):
template = """
Open modal task
Modal-local feedback
Use Alert for immediate feedback inside this task.
"""
css = ":where(.toast-example) { min-block-size:16rem; padding:1rem; }"
preview = ToastModalPause()
preview # noqa: B018
````
## Choose a logical corner
Placements are `block-start-start`, `block-start-end`, `block-end-start`, and
`block-end-end`. Logical edges follow direction and writing mode.
### Place Toasts in RTL
[Open the rendered preview](/ui-library/components/toast/_previews/placement-rtl/)
````citry
from typing import Any
import citry_ui
from citry import Component, citry
citry.register_library(citry_ui)
class ToastPlacementRtl(Component):
template = """
Logical start follows this RTL context.
"""
def template_data(self, kwargs: Any, slots: Any) -> dict[str, object]: # noqa: ARG002
return {"items": (citry_ui.CToastMessage(id="rtl", title="Logical start placement"),)}
css = ":where(.toast-example) { min-block-size:16rem; padding:1rem; }"
preview = ToastPlacementRtl()
preview # noqa: B018
````
## Customize the surface
Use documented variables and part selectors. Unlayered application CSS wins;
safe-area, narrow viewport, forced-colors, and print behavior stay owned.
### Customize Toast
[Open the rendered preview](/ui-library/components/toast/_previews/customization/)
````citry
from typing import Any
import citry_ui
from citry import Component, citry
citry.register_library(citry_ui)
class CustomizedToast(Component):
template = """
"""
def template_data(self, kwargs: Any, slots: Any) -> dict[str, object]: # noqa: ARG002
return {
"items": (
citry_ui.CToastMessage(
id="polar",
title="Polar archive synchronized",
description="A scheme-aware brand adaptation.",
intent="success",
),
)
}
css = """
:where(.toast-theme) { color-scheme:light dark; min-block-size:16rem; padding:1rem; }
:where(.polar-toast) {
--cui-toast-background: light-dark(#eef8fb, #102a34);
--cui-toast-foreground: light-dark(#17343e, #e6f7fb);
--cui-toast-border-color: light-dark(#76b7c7, #5ea5b6);
--cui-toast-radius: 1.25rem;
}
"""
preview = CustomizedToast()
preview # noqa: B018
````
## Composition boundaries
Toast is brief global feedback, not a task surface, form-error relationship,
arbitrary card renderer, or dismissible overlay. Use `CAlert` for persistent
rich content, `CDialog`/`CDrawer` for tasks, and Field/Form errors beside their
controls. V1 deliberately has no slots, imperative service, swipe, portal, or
multi-action layout.
## API reference
### Inputs
#### CToastRegion server inputs
Server inputs are passed in a template through ` ` or in Python through
`CToastRegion(...)`.
| Input | Type | Default | Effect |
|---|---|---|---|
| `items` | `Sequence[CToastMessage]` | `"()"` | Ordered initial queue copied and validated once per render. |
| `id` | `str | None` | generated | Sets exact Region identity and generated message relationships. |
| `label` | `non-empty str` | `"Notifications"` | Names the Region. |
| `messages` | `CToastMessages | None` ([`CToastMessages`](#toast-interface-toast-messages)) | `None` | Overrides catalog-backed dismiss and action-announcement patterns per field. |
| `placement` | `"block-start-start" | "block-start-end" | "block-end-start" | "block-end-end"` ([`CToastPlacement`](#toast-interface-placement)) | `"block-end-end"` | Selects a logical viewport corner. |
| `limit` | `int (1..10)` | `3` | Limits simultaneously presented messages. |
| `duration_ms` | `int` | `8000` | Sets default lifetime; zero is persistent and nonzero values are 1000..120000 milliseconds. |
| `pause_on_hover` | `bool` | `True` | Pauses remaining time while the viewport is hovered. |
| `pause_on_focus` | `bool` | `True` | Pauses remaining time while focus is inside. |
| `pause_on_hidden` | `bool` | `True` | Pauses remaining time while the owner document is hidden. |
| `class_` | `str | Mapping[str, bool] | Sequence[CClassValue] | None` ([`CClassValue`](#toast-interface-class-value)) | `None` | Merges consumer classes onto the Region. |
| `style` | `str | Mapping[str, str | int | float | bool | None] | Sequence[CStyleValue] | None` ([`CStyleValue`](#toast-interface-style-value)) | `None` | Merges consumer inline styles onto the Region. |
| `attrs` | `Mapping[str, object] | None` | `None` | Adds allowed native and data attributes without replacing owned semantics, focus, live regions, or structure. |
#### CToastRegion client inputs
Client inputs are passed in the browser through the `$c-props="{ ... }"` attribute on
` `.
| Input | Type | Omitted behavior | Effect |
|---|---|---|---|
| `items` | `Array` | Uses the copied server snapshot. | Reconciles the complete ordered queue by canonical ID. |
| `placement` | `"block-start-start" | "block-start-end" | "block-end-start" | "block-end-end"` ([`CToastPlacement`](#toast-interface-placement)) | Uses the server fallback. | Updates logical viewport placement. |
| `limit` | `integer (1..10)` | Uses the server fallback. | Changes visible capacity and promotes or queues messages. |
| `durationMs` | `integer` | Uses the server fallback. | Sets default lifetime for messages without an override. |
| `pauseOnHover` | `boolean` | Uses the server fallback. | Controls hover pause. |
| `pauseOnFocus` | `boolean` | Uses the server fallback. | Controls focus pause. |
| `pauseOnHidden` | `boolean` | Uses the server fallback. | Controls document-visibility pause. |
| `onDismiss` | `function` | Does not notify a component callback. | Receives timeout, explicit-dismiss, and action-dismiss completion. |
| `onAction` | `function` | Does not notify a component callback. | Receives the optional action before action-caused dismissal. |
### Slots
-
### Events
Component events are callback inputs supplied through `$c-props`. Native browser events
remain available through Alpine `@...` attributes.
#### CToastRegion events
| Event | Signature | Trigger and timing | Detail | Controlled and cancellation behavior |
|---|---|---|---|---|
| `onDismiss` | `(id: string, detail: CToastDismissDetail) => void` ([`CToastDismissDetail`](#toast-interface-toast-dismiss-detail)) | A presented message expires, is explicitly dismissed, or closes after its action. | `{reason: "timeout" | "dismiss" | "action", source: Element, message: CToastClientMessage}` ([`CToastDismissDetail`](#toast-interface-toast-dismiss-detail)) | Fires once after runtime removal; a producer should remove the ID to end suppression. |
| `onAction` | `(id: string, detail: CToastActionDetail) => void` ([`CToastActionDetail`](#toast-interface-toast-action-detail)) | The optional action Button activates. | `{source: HTMLButtonElement, message: CToastClientMessage}` ([`CToastActionDetail`](#toast-interface-toast-action-detail)) | Fires before an action-caused dismissal; stale work stops if the callback removes the Region. |
### Methods
-
### CSS
CSS variables to theme the components. Set them on an ancestor or the component itself.
#### CToastRegion CSS variables
Apply these variables to `CToastRegion` or one of its ancestors.
| Variable | Type | Purpose | Default |
|---|---|---|---|
| `--cui-toast-inline-offset` | `length` | Logical viewport inline offset. | `1rem` |
| `--cui-toast-block-offset` | `length` | Logical viewport block offset. | `1rem` |
| `--cui-toast-gap` | `length` | Visible stack gap. | `0.75rem` |
| `--cui-toast-width` | `length` | Preferred visible width. | `22rem` |
| `--cui-toast-background` | `color` | Message background. | `Canvas` |
| `--cui-toast-foreground` | `color` | Message foreground and controls. | `CanvasText` |
| `--cui-toast-border-color` | `color` | Message boundary. | `Subtle CanvasText mix.` |
| `--cui-toast-shadow` | `shadow` | Message elevation. | `0 1rem 3rem rgb(15 23 42 / 22%)` |
| `--cui-toast-radius` | `length` | Message corners. | `0.75rem` |
| `--cui-toast-padding` | `length` | Message padding. | `1rem` |
| `--cui-toast-accent` | `color` | Neutral message accent. | `currentColor` |
| `--cui-toast-z-index` | `integer` | Nonmodal application stacking hint. | `1000` |
### Attributes
HTML attributes defined on the components that you can refer to for CSS, inspection, and
testing. Read-only.
#### CToastRegion attributes
| Attribute | Element | Type | Meaning |
|---|---|---|---|
| `data-placement` | Region | `logical placement` | Mirrors the effective viewport corner. |
| `data-paused` | Region | `present | absent` | Present while timers are paused by hover, focus, visibility, or modality. |
| `data-intent` | Toast | `neutral | info | success | warn | error` | Mirrors presentation intent. |
| `data-priority` | Toast | `polite | assertive` | Mirrors announcement urgency. |
### Selectors
Selectors for the DOM nodes in the components that you can use for CSS, inspection, and
testing.
#### CToastRegion selectors
| Selector | Element | Purpose |
|---|---|---|
| `[data-citry-ui-part="region"]` | Region section | Viewport and attrs destination. |
| `[data-citry-ui-part="announcer-polite"]` | Hidden live region | Serialized polite announcements. |
| `[data-citry-ui-part="announcer-assertive"]` | Hidden live region | Serialized assertive announcements. |
| `[data-citry-ui-part="toast"]` | Presented group | Focusable message surface. |
| `[data-citry-ui-part="content"]` | Content wrapper | Title and optional description. |
| `[data-citry-ui-part="title"]` | Title | Visible accessible name. |
| `[data-citry-ui-part="description"]` | Description | Optional relationship text. |
| `[data-citry-ui-part="actions"]` | Controls wrapper | Optional action and dismissal layout. |
| `[data-citry-ui-part="action"]` | Button | Optional one-action control. |
| `[data-citry-ui-part="dismiss"]` | Button | Explicit message dismissal. |
### Interfaces
Aliases and data shapes referenced above.
#### Input type aliases
| Interface | Definition |
|---|---|
| `CClassValue` | `str | Mapping[str, bool] | Sequence[CClassValue]` |
| `CStyleValue` | `str | Mapping[str, str | int | float | bool | None] | Sequence[CStyleValue]` |
| `CToastIntent` | `Literal["neutral", "info", "success", "warn", "error"]` |
| `CToastPlacement` | `Literal["block-start-start", "block-start-end", "block-end-start", "block-end-end"]` |
| `CToastPriority` | `Literal["polite", "assertive"]` |
#### `CToastMessage`
| Field | Type | Default | Meaning |
|---|---|---|---|
| `id` | `str` | - | Unique canonical queue identity. |
| `title` | `str` | - | Visible and accessible plain-text name. |
| `description` | `str | None` | - | Optional plain supporting text. |
| `intent` | `CToastIntent` | - | Presentation intent independent from urgency. |
| `priority` | `CToastPriority` | - | Polite or assertive announcement channel. |
| `duration_ms` | `int | None` | - | Optional per-message lifetime; zero is persistent. |
| `action_label` | `str | None` | - | Optional one-action Button label. |
| `close_on_action` | `bool` | - | Whether action completion dismisses the message. |
| `dismissible` | `bool` | - | Whether explicit dismissal is available. |
#### `CToastMessages`
| Field | Type | Default | Meaning |
|---|---|---|---|
| `dismiss_label` | `str | None` | None | Overrides the catalog-backed dismiss pattern and must contain `{title}`. |
| `action_announcement` | `str | None` | None | Overrides the catalog-backed action announcement and must contain `{action_label}`. |
#### `CToastClientMessage`
| Field | Type | Default | Meaning |
|---|---|---|---|
| `id` | `string` | - | Unique canonical queue identity. |
| `title` | `string` | - | Visible and accessible plain-text name. |
| `description` | `string | null` | - | Optional supporting text. |
| `intent` | `CToastIntent` | - | Presentation intent. |
| `priority` | `CToastPriority` | - | Announcement channel. |
| `durationMs` | `integer | null` | - | Optional lifetime override. |
| `actionLabel` | `string | null` | - | Optional one-action label. |
| `closeOnAction` | `boolean` | - | Whether action dismisses. |
| `dismissible` | `boolean` | - | Whether explicit dismissal is available. |
#### `CToastDismissDetail`
| Field | Type | Default | Meaning |
|---|---|---|---|
| `reason` | `"timeout" | "dismiss" | "action"` | - | Runtime removal reason. |
| `source` | `Element` | - | Browser source associated with dismissal. |
| `message` | `CToastClientMessage` | - | Canonical public message snapshot. |
#### `CToastActionDetail`
| Field | Type | Default | Meaning |
|---|---|---|---|
| `source` | `HTMLButtonElement` | - | Activated action Button. |
| `message` | `CToastClientMessage` | - | Canonical public message snapshot. |
### Translation keys
Catalog keys used by this family. An explicit component input or slot listed in Override
takes precedence over the catalog for that instance.
#### CToastRegion translation keys
| Key | Purpose | Variables | Override | Browser updates |
|---|---|---|---|---|
| `citry-ui-toast-region` | Names the notification region. | `None` | `label` input | $c-tr updates `aria-label`. |
| `citry-ui-toast-dismiss` | Names each toast dismiss control. | `title: str` | `messages.dismiss_label` | $c-tr handles initial items; `i18n.bind()` handles browser-created items. |
| `citry-ui-toast-action-available` | Announces that the toast exposes an action. | `action_label: str` | `messages.action_announcement` | One-shot `i18n.tr()` when the toast is added. |
---
# Accordion
Source: https://citry.dev/ui-library/components/accordion/
# Accordion
Use `CAccordion` for a finite group of related sections. Each
`CAccordionItem` renders a native heading and button. Panel content stays in
the document when closed, preserving forms, browser-owned values, and nested
component state.
## Accordion at a glance
Open the field-guide sections to see the complete item pattern in a compact
group.
### Accordion at a glance
[Open the rendered preview](/ui-library/components/accordion/_previews/at-a-glance/)
````citry
import citry_ui
from citry import Component, citry
citry.register_library(citry_ui)
class AccordionAtAGlance(Component):
template = """
Temperate rainforest
Layers of the forest
Canopy
Interlocking crowns collect most sunlight and shelter the layers below.
Understory
Ferns, saplings, and mosses thrive in filtered green light.
Forest floor
Fungi and invertebrates return fallen wood to the soil.
"""
css = """
:where(.forest-guide) {
display: grid;
gap: 1rem;
max-width: 46rem;
color: CanvasText;
font-family: ui-sans-serif, system-ui, sans-serif;
}
:where(.forest-guide h2, .forest-guide p) {
margin: 0;
}
:where(.forest-guide header > p) {
color: light-dark(#2f6b45, #86d29e);
font-size: 0.75rem;
font-weight: 700;
letter-spacing: 0.08em;
text-transform: uppercase;
}
"""
preview = AccordionAtAGlance()
preview # noqa: B018
````
## Compose Accordion items
Give every item a stable `value`, a `title` fill, and a default panel fill.
### Compose an Accordion
[Open the rendered preview](/ui-library/components/accordion/_previews/basic-accordion/)
````citry
import citry_ui
from citry import Component, citry
citry.register_library(citry_ui)
class BasicAccordion(Component):
template = """
Moss gardens
Moss retains moisture around roots and fallen logs.
Cold streams
Shaded water stays cool enough for salmon and stoneflies.
Nurse logs
Seedlings use decaying trunks as raised, nutrient-rich beds.
"""
preview = BasicAccordion()
preview # noqa: B018
````
```citry-html
Forest canopy
The canopy captures most incoming sunlight.
Understory
Shade-tolerant plants grow beneath the canopy.
```
For Python composition, supply one component whose output contains the direct
items. This preserves item registration without introducing a DOM wrapper.
```python
from citry import Component
from citry_ui import CAccordion
class FieldGuideItems(Component):
template = """
Forest canopy
Upper forest layer
"""
field_guide = CAccordion(
value="canopy",
slots={"default": FieldGuideItems()},
)
```
`CAccordionItem` is not standalone. Put it directly inside the nearest
Accordion. Transparent components may generate items when they add no wrapper
or other output.
## Control expansion in the browser
Server inputs are passed in Python through ` ` attributes
or a `CAccordion(...)` composition call. Client inputs are passed in the
browser through `$c-props="{...}"`.
### Control Accordion value
[Open the rendered preview](/ui-library/components/accordion/_previews/controlled-value/)
````citry
import citry_ui
from citry import Component, citry
citry.register_library(citry_ui)
class ControlledAccordion(Component):
template = """
Open section: lichen
selected = value,
}"
>
Lichen
A partnership between fungi and algae.
Mushrooms
Temporary fruiting bodies of hidden fungal networks.
Ferns
Ancient plants that reproduce through spores.
"""
css = """
:where(.controlled-accordion) {
display: grid;
gap: 0.75rem;
max-width: 44rem;
color: CanvasText;
font-family: ui-sans-serif, system-ui, sans-serif;
}
:where(.controlled-accordion > p) {
margin: 0;
color: light-dark(#356548, #8bcda0);
}
"""
preview = ControlledAccordion()
preview # noqa: B018
````
Single mode uses `string | null`; multiple mode uses `string[] | null`.
`onValueChange` receives requests before an uncontrolled commit. When `value`
is supplied, update it in the callback to accept the request. Omit the client
value to release control without resetting the current valid browser state.
## Choose an expansion policy
The default single mode keeps at most one panel open. Set `multiple=True` to
open several. Set `collapsible=False` in single mode when an open item should
stay open.
### Compare expansion modes
[Open the rendered preview](/ui-library/components/accordion/_previews/expansion-modes/)
````citry
import citry_ui
from citry import Component, citry
citry.register_library(citry_ui)
class ExpansionModes(Component):
template = """
One section, always open
Root network
Roots trade nutrients with underground fungi.
Living soil
A pinch of soil can hold billions of organisms.
Several sections
Western red cedar
Scale-like leaves stay green through winter.
Western hemlock
Drooping leaders distinguish its young crowns.
"""
css = """
:where(.expansion-modes) {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(min(100%, 18rem), 1fr));
gap: 1rem;
color: CanvasText;
font-family: ui-sans-serif, system-ui, sans-serif;
}
:where(.expansion-modes article) {
min-width: 0;
}
:where(.expansion-modes h2) {
margin-block: 0 0.625rem;
font-size: 1rem;
}
"""
preview = ExpansionModes()
preview # noqa: B018
````
`collapsible=False` does not force an initial selection. After a section opens,
its trigger remains focusable and exposes `aria-disabled="true"` while it is
the item that cannot close.
## Add adjacent actions
Put related Buttons, links, or menus in the `actions` slot. They render beside
the heading, never inside its trigger. `actions_label` creates one named
`group` for the controls.
### Add item actions
[Open the rendered preview](/ui-library/components/accordion/_previews/actions/)
````citry
import citry_ui
from citry import Component, citry
citry.register_library(citry_ui)
class AccordionActions(Component):
template = """
River trail
Map
Save
A shaded six-kilometre route follows the river upstream.
Ridge trail
Map
An exposed climb reaches the old fire lookout.
"""
preview = AccordionActions()
preview # noqa: B018
````
Title content is inside a native button. Keep it to noninteractive phrasing
content. Links, form controls, nested headings, and another Accordion belong in
the panel or actions slot.
## Disable groups or items
Group `disabled` blocks every trigger. Item `disabled` blocks only that item.
An open disabled item stays open.
### Disable Accordion items
[Open the rendered preview](/ui-library/components/accordion/_previews/disabled-items/)
````citry
import citry_ui
from citry import Component, citry
citry.register_library(citry_ui)
class DisabledItems(Component):
template = """
Fern loop
Open from dawn until dusk.
Cedar crossing — temporarily closed
High water has covered the footbridge.
Wetland boardwalk
A level route through reeds and alder groves.
"""
preview = DisabledItems()
preview # noqa: B018
````
An enclosing disabled native `fieldset`, including CForm's fieldset, remains
authoritative. Client `disabled=False` cannot re-enable its descendant
buttons.
## Nest Accordion
Put a nested `CAccordion` in a panel. Do not place it in a title or action
area. The nested root becomes a new registration and keyboard boundary.
### Nest Accordion groups
[Open the rendered preview](/ui-library/components/accordion/_previews/nested-accordion/)
````citry
import citry_ui
from citry import Component, citry
citry.register_library(citry_ui)
class NestedAccordion(Component):
template = """
Trees
Compare two trees found along the valley trail.
Western red cedar
A long-lived tree of moist lowland forests.
Bigleaf maple
Broad leaves support hanging gardens of moss.
Wildflowers
Trillium and violets bloom before the canopy closes.
"""
css = """
:where([data-citry-ui-part="accordion-body"] > p:first-child) {
margin-block-start: 0;
}
"""
preview = NestedAccordion()
preview # noqa: B018
````
## Choose variant and size
`outline`, `soft`, `separated`, and `plain` cover connected and independent
surfaces. `sm`, `md`, and `lg` change trigger, action, indicator, and panel
geometry.
### Compare variants and sizes
[Open the rendered preview](/ui-library/components/accordion/_previews/variants/)
````citry
import citry_ui
from citry import Component, citry
citry.register_library(citry_ui)
class AccordionVariants(Component):
class Kwargs:
pass
class Slots:
pass
template = """
{{ variant }}
Rainfall
Frequent mist keeps the forest green.
Filtered light
Sunflecks move across the understory.
Sizes
{{ size }}
Moss cover
Soft ground holds overnight rain.
"""
def template_data(
self,
kwargs: Kwargs, # noqa: ARG002
slots: Slots, # noqa: ARG002
) -> dict[str, object]:
return {
"variants": ("outline", "soft", "separated", "plain"),
"sizes": ("sm", "md", "lg"),
}
css = """
:where(.accordion-variants) {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(min(100%, 18rem), 1fr));
gap: 1.25rem;
color: CanvasText;
font-family: ui-sans-serif, system-ui, sans-serif;
}
:where(.accordion-variants article) {
min-width: 0;
}
:where(.accordion-variants__sizes) {
grid-column: 1 / -1;
}
:where(.accordion-variants__size-grid) {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(min(100%, 14rem), 1fr));
gap: 1rem;
}
:where(.accordion-variants h2) {
margin-block: 0 0.625rem;
font-size: 0.875rem;
text-transform: capitalize;
}
:where(.accordion-variants h3) {
margin-block: 0 0.5rem;
font-size: 0.75rem;
text-transform: uppercase;
}
"""
preview = AccordionVariants()
preview # noqa: B018
````
## Customize Accordion
Override public variables on an ancestor or one root. Stable part selectors
target item anatomy. Browser inputs can change `variant`, `size`, `indicator`,
and `indicatorPosition` without a server render.
### Theme a field guide
[Open the rendered preview](/ui-library/components/accordion/_previews/customization/)
````citry
import citry_ui
from citry import Component, citry
citry.register_library(citry_ui)
class CustomizeAccordion(Component):
template = """
Live configuration
Forest field guide
Watershed
Every hillside stream eventually meets the river.
Wildlife corridor
Connected forest lets animals move between habitats.
"""
css = """
:where(.accordion-configurator) {
display: grid;
gap: 1rem;
max-width: 48rem;
color: CanvasText;
font-family: ui-sans-serif, system-ui, sans-serif;
}
:where(.accordion-configurator h2, .accordion-configurator p) {
margin: 0;
}
:where(.accordion-configurator header > p) {
color: light-dark(#39724e, #8fd4a6);
font-size: 0.75rem;
font-weight: 700;
letter-spacing: 0.08em;
text-transform: uppercase;
}
:where(.accordion-configurator__group) {
--cui-accordion-radius: 1rem;
--cui-accordion-trigger-open-color: light-dark(#1f6b3c, #8fe0aa);
--cui-accordion-focus-color: light-dark(#2f855a, #70d397);
}
"""
preview_controls = (
{
"name": "variant",
"label": "Variant",
"type": "select",
"default": "separated",
"options": (
("outline", "Outline"),
("soft", "Soft"),
("separated", "Separated"),
("plain", "Plain"),
),
},
{
"name": "size",
"label": "Size",
"type": "select",
"default": "md",
"options": (("sm", "Small"), ("md", "Medium"), ("lg", "Large")),
},
{
"name": "indicator_position",
"label": "Indicator position",
"type": "select",
"default": "end",
"options": (("start", "Start"), ("end", "End")),
},
{
"name": "indicator",
"label": "Show indicator",
"type": "checkbox",
"default": True,
},
)
preview = CustomizeAccordion()
preview # noqa: B018
````
`class_`, `style`, and `attrs` target the Accordion root. An item has its own
`class_`, `style`, and `attrs`, plus exact maps for its native heading,
trigger, panel, and optional actions wrapper. Unlayered consumer CSS overrides
Citry UI defaults; named layers follow the site-wide layer-order contract.
## Keyboard and accessibility
Every enabled trigger remains in normal Tab order. Enter and Space use native
button activation. Arrow Up, Arrow Down, Home, and End move focus among enabled
triggers without opening them; `loop` controls wrapping.
Choose `heading_level` to fit the page outline. Panels are neutral by default.
Set `region=True` only when the panels benefit from landmarks; this adds
`role="region"` and trigger-based naming as one pair.
Closing a panel that contains focus moves focus to its trigger before the panel
becomes inert. A structural update that removes the focused item moves focus to
the nearest enabled surviving trigger. If none survives, the update owner must
choose an external destination.
## Forms, animation, and content lifetime
Closed panel content remains mounted. Uncontrolled edits, successful controls,
and nested component state survive close and reopen. Closed controls still
belong to `FormData` and native constraint validation. A hidden required
control can therefore block submission; applications must open the relevant
panel before moving focus to it.
Rapid expansion requests replace the active animation instead of being
ignored. Reduced-motion users receive an immediate commit. Settled panels do
not clip overlays; a panel clips its contents only during the bounded height
transition. Print shows every panel.
## Trust boundaries
Item values and generated IDs are plain text. Raw values appear only in the
public `data-value`; generated trigger/panel IDs use a stable hash. Title and
panel content use ordinary Citry escaping. The chevron comes from the packaged
icon allowlist.
Attribute maps are trusted authoring surfaces for unowned values. Accordion
rejects attributes and Alpine directives that could replace native semantics,
children, expansion visibility, focus ownership, a second popover/command
activation owner, public mirrors, or Citry runtime markers.
## API reference
### Inputs
#### CAccordion server inputs
Server inputs are passed in a template through ` ` or in Python through
`CAccordion(...)`.
| Input | Type | Default | Effect |
|---|---|---|---|
| `value` | `str | Sequence[str] | None` | `None` | Sets initial expansion. Single mode accepts one value; multiple mode accepts a duplicate-free sequence. |
| `multiple` | `bool` | `False` | Uses an ordered array value and allows several panels to remain open. |
| `collapsible` | `bool` | `True` | Allows the open item to close in single mode. Multiple mode always remains collapsible. |
| `disabled` | `bool` | `False` | Disables every trigger without closing panels. An enclosing disabled Form or fieldset remains dominant. |
| `loop` | `bool` | `True` | Wraps optional Arrow Up and Arrow Down focus navigation. |
| `variant` | `"outline" | "soft" | "separated" | "plain"` ([`CAccordionVariant`](#accordion-interface-accordion-variant)) | `"outline"` | Selects connected border, quiet surface, separated card, or divider treatment. |
| `size` | `"sm" | "md" | "lg"` ([`CAccordionSize`](#accordion-interface-accordion-size)) | `"md"` | Selects title, indicator, action, and panel geometry. |
| `indicator` | `bool` | `True` | Shows the owned decorative chevron. |
| `indicator_pos` | `"start" | "end"` ([`CAccordionIndicatorPos`](#accordion-interface-accordion-indicator-pos)) | `"end"` | Places the chevron at the logical start or end of every trigger. |
| `heading_level` | `Literal[2, 3, 4, 5, 6]` ([`CAccordionHeadingLevel`](#accordion-interface-accordion-heading-level)) | `3` | Chooses the native heading level for every direct item. |
| `region` | `bool` | `False` | Adds `role="region"` and trigger-based naming to every panel. Use selectively to avoid landmark proliferation. |
| `id` | `str | None` | `None` | Sets the root ID and stable trigger/panel ID prefix. |
| `class_` | `str | Mapping[str, bool] | Sequence[CClassValue] | None` ([`CClassValue`](#accordion-interface-accordion-class-value)) | `None` | Adds root classes and merges them with `attrs`. |
| `style` | `str | Mapping[str, str | int | float | bool | None] | Sequence[CStyleValue] | None` ([`CStyleValue`](#accordion-interface-accordion-style-value)) | `None` | Adds root inline styles and merges them with `attrs`. |
| `attrs` | `Mapping[str, object] | None` | `None` | Adds trusted unowned root attributes. Accordion semantics, focus, alternate visibility or overlay ownership, public mirrors, structure, and runtime namespaces are reserved. |
#### CAccordion client inputs
Client inputs are passed in the browser through the `$c-props="{ ... }"` attribute on
` `.
| Input | Type | Omitted behavior | Effect |
|---|---|---|---|
| `value` | `string | null; string[] | null in multiple mode` | Releases client control and preserves the current valid browser state. | Controls expansion with the mode-dependent public shape. |
| `onValueChange` | `function` | No callback. | Receives accepted activation and structural-removal requests. |
| `collapsible` | `boolean` | Uses the server input. | Controls whether the open single item may close. |
| `disabled` | `boolean` | Uses the server input. | Controls group disabledness below native Form or fieldset ownership. |
| `loop` | `boolean` | Uses the server input. | Controls Arrow-key wrapping. |
| `variant` | `"outline" | "soft" | "separated" | "plain"` ([`CAccordionVariant`](#accordion-interface-accordion-variant)) | Uses the server input. | Controls visual treatment. |
| `size` | `"sm" | "md" | "lg"` ([`CAccordionSize`](#accordion-interface-accordion-size)) | Uses the server input. | Controls geometry. |
| `indicator` | `boolean` | Uses the server input. | Controls chevron visibility. |
| `indicatorPosition` | `"start" | "end"` ([`CAccordionIndicatorPos`](#accordion-interface-accordion-indicator-pos)) | Uses the server input. | Controls logical chevron placement. |
#### CAccordionItem server inputs
Server inputs are passed in a template through ` ` or in Python
through `CAccordionItem(...)`.
| Input | Type | Default | Effect |
|---|---|---|---|
| `value` | `non-empty str` | required | Supplies stable item identity. Values must be unique within the nearest Accordion. |
| `disabled` | `bool` | `False` | Disables this trigger without closing its panel. |
| `actions_label` | `non-whitespace str | None` | `None` | Names the optional action group and emits its owned `group` role; requires actions. |
| `class_` | `str | Mapping[str, bool] | Sequence[CClassValue] | None` ([`CClassValue`](#accordion-interface-accordion-class-value)) | `None` | Adds item-root classes and merges them with `attrs`. |
| `style` | `str | Mapping[str, str | int | float | bool | None] | Sequence[CStyleValue] | None` ([`CStyleValue`](#accordion-interface-accordion-style-value)) | `None` | Adds item-root inline styles and merges them with `attrs`. |
| `attrs` | `Mapping[str, object] | None` | `None` | Adds trusted unowned item-root attributes. |
| `heading_attrs` | `Mapping[str, object] | None` | `None` | Adds trusted unowned native-heading attributes. |
| `trigger_attrs` | `Mapping[str, object] | None` | `None` | Adds trusted unowned native-button attributes and event listeners. Button semantics, activation ownership, and state are reserved. |
| `panel_attrs` | `Mapping[str, object] | None` | `None` | Adds trusted unowned panel attributes. Visibility, region semantics, ID, and state are reserved. |
| `actions_attrs` | `Mapping[str, object] | None` | `None` | Adds trusted unowned action-wrapper attributes. Requires actions; group naming and focus/live-region ownership are reserved. |
#### CAccordionItem client inputs
Client inputs are passed in the browser through the `$c-props="{ ... }"` attribute on
` `.
| Input | Type | Omitted behavior | Effect |
|---|---|---|---|
| `disabled` | `boolean` | Uses the server input. | Controls this item's disabledness below group and native fieldset ownership. |
### Slots
Slots are passed as nested content or `` tags in a template, or through the
`slots={...}` argument in Python.
#### CAccordion slots
| Slot | Required | Data | Fallback |
|---|---|---|---|
| `default` | yes | `{}` ([`CAccordionDefaultSlotData`](#accordion-interface-accordion-slot-data)) | None. Requires one or more direct CAccordionItem components. |
#### CAccordionItem slots
| Slot | Required | Data | Fallback |
|---|---|---|---|
| `title` | yes | `{}` ([`CAccordionItemTitleSlotData`](#accordion-interface-accordion-item-title-slot-data)) | None. Renders inside the native trigger and accepts noninteractive phrasing content. |
| `default` | yes | `{}` ([`CAccordionItemDefaultSlotData`](#accordion-interface-accordion-item-default-slot-data)) | None. Renders as always-mounted panel flow content. |
| `actions` | no | `{}` ([`CAccordionItemActionsSlotData`](#accordion-interface-accordion-item-actions-slot-data)) | No adjacent action wrapper. |
### Events
Component events are callback inputs supplied through `$c-props`. Native browser events
remain available through Alpine `@...` attributes.
#### CAccordion events
| Event | Signature | Trigger and timing | Detail | Controlled and cancellation behavior |
|---|---|---|---|---|
| `onValueChange` | `(value: string | null | string[], detail: CAccordionValueChangeDetail) => void` | Accepted trigger activation or one batched structural-removal fallback. Initial state and owner prop updates are excluded. | `{value, previousValue, itemValue: string | null, removedValues: string[], expanded: boolean, source: "activation" | "removal"}` | Runs before an uncontrolled commit. Controlled Accordion waits for `value`; return values do not cancel the request. |
### Methods
-
### CSS
CSS variables to theme the components. Set them on an ancestor or the component itself.
#### CAccordion CSS variables
Apply these variables to `CAccordion` or one of its ancestors.
| Variable | Type | Purpose | Default |
|---|---|---|---|
| `--cui-accordion-background` | `color` | Connected root and item surface. | `` `Canvas` `` |
| `--cui-accordion-foreground` | `color` | Title and panel foreground. | `` `CanvasText` `` |
| `--cui-accordion-border-color` | `color` | Root item and divider boundary. | `22% current-color mix.` |
| `--cui-accordion-border-width` | `length` | Stable border geometry. | `1px` |
| `--cui-accordion-radius` | `length` | Group and separated-item corners. | `0.75rem` |
| `--cui-accordion-gap` | `length` | Separated-item gap. | `0.75rem` |
| `--cui-accordion-shadow` | `shadow` | Separated-item elevation. | `Scheme-derived shadow.` |
| `--cui-accordion-trigger-background` | `color` | Resting trigger background. | `transparent` |
| `--cui-accordion-trigger-hover-background` | `color` | Enabled hover background. | `8% current-color mix.` |
| `--cui-accordion-trigger-open-background` | `color` | Expanded trigger background. | `9% LinkText mix.` |
| `--cui-accordion-trigger-open-color` | `color` | Expanded title and chevron foreground. | `` `LinkText` `` |
| `--cui-accordion-focus-color` | `color` | Trigger focus ring. | `` `Highlight` `` |
| `--cui-accordion-indicator-color` | `color` | Chevron foreground. | `currentColor` |
| `--cui-accordion-trigger-padding-inline` | `length` | Trigger inline inset. | `Size-derived.` |
| `--cui-accordion-trigger-padding-block` | `length` | Trigger block inset. | `Size-derived.` |
| `--cui-accordion-panel-padding-inline` | `length` | Panel-body inline inset. | `Size-derived.` |
| `--cui-accordion-panel-padding-block` | `length` | Panel-body block inset. | `Size-derived.` |
| `--cui-accordion-actions-gap` | `length` | Adjacent action spacing. | `0.5rem` |
| `--cui-accordion-duration` | `time` | Panel and chevron transition duration. | `180ms` |
| `--cui-accordion-easing` | `easing` | Panel and chevron transition curve. | `ease-out` |
### Attributes
HTML attributes defined on the components that you can refer to for CSS, inspection, and
testing. Read-only.
#### CAccordion attributes
| Attribute | Element | Type | Meaning |
|---|---|---|---|
| `data-variant` | Root | `"outline" | "soft" | "separated" | "plain"` | Mirrors effective visual treatment. |
| `data-size` | Root | `"sm" | "md" | "lg"` | Mirrors effective geometry. |
| `data-multiple` | Root | `present | absent` | Present in structural multiple mode. |
| `data-collapsible` | Root | `present | absent` | Present while open items may close; always present in multiple mode. |
| `data-disabled` | Root | `present | absent` | Mirrors browser-effective group disabledness. |
| `data-loop` | Root | `present | absent` | Present while Arrow navigation wraps. |
| `data-indicator` | Root | `present | absent` | Present while chevrons are visible. |
| `data-indicator-pos` | Root | `"start" | "end"` | Mirrors logical chevron placement. |
| `id` | Root | `str` | Uses the supplied root ID or a generated instance ID. |
#### CAccordionItem attributes
| Attribute | Element | Type | Meaning |
|---|---|---|---|
| `data-state` | Item, trigger, and panel | `"open" | "closed"` | Mirrors committed expansion. |
| `data-disabled` | Item and trigger | `present | absent` | Mirrors browser-effective item disabledness. |
| `data-value` | Item | `str` | Exposes canonical item identity for styling and inspection. |
| `aria-expanded` | Trigger | `"true" | "false"` | Exposes native expansion state. |
| `aria-disabled` | Trigger | `absent | "true"` | Marks an otherwise enabled open trigger that cannot collapse. |
| `disabled` | Trigger | `present | absent` | Present for component-owned group or item disabledness. A native fieldset can also disable the trigger without adding this attribute. |
| `id` | Trigger and panel | `generated str` | Derives a stable relationship pair from the root ID and canonical item value. |
| `aria-controls` | Trigger | `panel IDREF` | Identifies the panel controlled by this trigger. |
| `aria-labelledby` | Panel | `absent | trigger IDREF` | Names a region panel from its trigger only when region mode is enabled. |
| `aria-hidden` | Panel | `absent | "true"` | Present while the panel is collapsed. |
| `inert` | Panel | `present | absent` | Removes collapsed panel descendants from focus and interaction. |
| `hidden` | Panel | `present | absent` | Removes a settled collapsed panel from rendering. |
| `hidden` | Indicator wrapper | `present | absent` | Removes the chevron when indicator is disabled. |
| `role` | Panel | `absent | "region"` | Present with aria-labelledby only when region mode is enabled. |
### Selectors
Selectors for the DOM nodes in the components that you can use for CSS, inspection, and
testing.
#### CAccordion selectors
| Selector | Element | Purpose |
|---|---|---|
| `[data-citry-ui-part="accordion"]` | Root div | Group surface and class/style/attrs destination. |
| `[data-citry-ui-part="accordion-item"]` | Item root div | Keyed item surface and item attrs destination. |
| `[data-citry-ui-part="accordion-header"]` | Header row div | Heading and adjacent-action layout. |
| `[data-citry-ui-part="accordion-heading"]` | Native h2-h6 | Document-outline heading and heading_attrs destination. |
| `[data-citry-ui-part="accordion-trigger"]` | Native button | Expansion control and trigger_attrs destination. |
| `[data-citry-ui-part="accordion-title"]` | Trigger title span | Visible title and accessible-name content. |
| `[data-citry-ui-part="accordion-indicator"]` | Decorative span | Owned chevron wrapper. |
| `[data-citry-ui-part="accordion-actions"]` | Optional adjacent-action div | Action layout naming and actions_attrs destination. |
| `[data-citry-ui-part="accordion-panel"]` | Controlled panel div | Visibility region semantics and panel_attrs destination. |
| `[data-citry-ui-part="accordion-body"]` | Panel-body div | Content inset and overflow-neutral surface. |
### Interfaces
Aliases and data shapes referenced above.
#### Input type aliases
| Interface | Definition |
|---|---|
| `CClassValue` | `str | Mapping[str, bool] | Sequence[CClassValue]` |
| `CStyleValue` | `str | Mapping[str, str | int | float | bool | None] | Sequence[CStyleValue]` |
| `CAccordionVariant` | `Literal["outline", "soft", "separated", "plain"]` |
| `CAccordionSize` | `Literal["sm", "md", "lg"]` |
| `CAccordionIndicatorPos` | `Literal["start", "end"]` |
| `CAccordionHeadingLevel` | `Literal[2, 3, 4, 5, 6]` |
| `CAccordionValueChangeDetail` | `{value: string | null | string[], previousValue: string | null | string[], itemValue: string | null, removedValues: string[], expanded: boolean, source: "activation" | "removal"}` |
#### `CAccordionDefaultSlotData`
Empty dataclass: `{}`.
#### `CAccordionItemTitleSlotData`
Empty dataclass: `{}`.
#### `CAccordionItemDefaultSlotData`
Empty dataclass: `{}`.
#### `CAccordionItemActionsSlotData`
Empty dataclass: `{}`.
### Translation keys
-
---
# AlertDialog
Source: https://citry.dev/ui-library/components/alert-dialog/
# AlertDialog
Use `CAlertDialog` when a consequential action needs an immediate explicit
decision. It requires a visible title, concise description, Cancel control,
and Action control. Use `CAlert` for persistent feedback and `CDialog` for
general modal content, forms, or more than two decisions.
### AlertDialog at a glance
[Open the rendered preview](/ui-library/components/alert-dialog/_previews/at-a-glance/)
````citry
import citry_ui
from citry import Component, citry
citry.register_library(citry_ui)
class AlertDialogGlance(Component):
template = """
Delete project
Delete this project?
This permanently removes all project data.
Keep project
Delete
"""
preview = AlertDialogGlance()
preview # noqa: B018
````
```citry-html
Delete project
Delete this project?
This permanently removes all project data.
Keep project
Delete
```
## Choose the right interruption
AlertDialog is intentionally narrow. The native surface has
`role="alertdialog"`, a required name and description, and exactly two owned
decision regions. Outside presses never close it. Escape acts like Cancel when
`close_on_escape=True`.
### Acknowledge a blocking error
[Open the rendered preview](/ui-library/components/alert-dialog/_previews/blocking-error/)
````citry
import citry_ui
from citry import Component, citry
citry.register_library(citry_ui)
class BlockingError(Component):
template = """
Show sync error
Changes could not be synchronized
Reconnect before continuing so this draft is not overwritten.
Your local draft remains available in this browser.
Review draft
Retry connection
"""
preview = BlockingError()
preview # noqa: B018
````
## Control asynchronous decisions
A supplied client `open` Boolean is authoritative. `onOpenChange` requests the
next state; accept it when application work is ready. Cancel and Action both
use `reason="action"`; inspect `detail.returnValue` for `"cancel"` or
`"action"`.
### Control an asynchronous decision
[Open the rendered preview](/ui-library/components/alert-dialog/_previews/controlled-action/)
````citry
import citry_ui
from citry import Component, citry
citry.register_library(citry_ui)
class ControlledArchive(Component):
template = """
{
if (detail.returnValue === 'action') {
pending = true;
result = 'Archiving...';
setTimeout(() => {
pending = false;
open = false;
result = 'Record archived';
}, 500);
} else {
open = next;
if (!next) result = 'Archive cancelled';
}
}
}"
>
Archive record
Archive this record?
It will leave the active workspace.
Cancel
Archive
"""
preview = ControlledArchive()
preview # noqa: B018
````
```citry-html
{
if (detail.returnValue === 'action') archiveThenClose()
else confirming = open
}
}"
>
...
```
Omit or supply `null` for the client `open` prop to release control while
preserving the effective state.
## Compose native Buttons safely
`CButton` already owns `type="button"`; pass only `*_attrs` to it. A native
Button must consume both the attribute mapping and adjacent type field.
### Use native decision Buttons
[Open the rendered preview](/ui-library/components/alert-dialog/_previews/native-buttons/)
````citry
import citry_ui
from citry import Component, citry
citry.register_library(citry_ui)
class NativeAlertButtons(Component):
template = """
Leave editor
Leave the editor?
Changes since the last save will be lost.
Stay
Leave
"""
preview = NativeAlertButtons()
preview # noqa: B018
````
```citry-html
Stay
```
Native click handlers run before the component open-change request. This lets
the application perform or schedule domain work without a duplicate custom
confirm event.
## Focus and accessibility
Cancel receives initial focus so the destructive choice is never the default.
Tab and Shift+Tab remain inside the modal. Closing restores the connected
activator unless application code deliberately moved focus elsewhere. The
required title and description become the exact `aria-labelledby` and
`aria-describedby` targets.
## Size and customization
Sizes are `sm`, `md`, and `lg`; `sm` is the default. Full-screen workflows
belong to Dialog. AlertDialog shares Dialog layout behavior while exposing
family-specific variables.
### Compare AlertDialog sizes
[Open the rendered preview](/ui-library/components/alert-dialog/_previews/sizes/)
````citry
import citry_ui
from citry import Component, citry
citry.register_library(citry_ui)
class AlertDialogSizes(Component):
class Kwargs:
pass
class Slots:
pass
template = """
Open {{ size }}
{{ size }} decision surface
Compare the responsive width for this size.
Cancel
Continue
"""
def template_data(self, kwargs: Kwargs, slots: Slots) -> dict[str, object]: # noqa: ARG002
return {"sizes": ("sm", "md", "lg")}
preview = AlertDialogSizes()
preview # noqa: B018
````
### Customize AlertDialog
[Open the rendered preview](/ui-library/components/alert-dialog/_previews/customization/)
````citry
import citry_ui
from citry import Component, citry
citry.register_library(citry_ui)
class CustomizedAlertDialog(Component):
template = """
Archive workspace
Archive this workspace?
Collaborators will lose active access.
Keep active
Archive
"""
preview = CustomizedAlertDialog()
preview # noqa: B018
````
```css
.archive-alert {
--cui-alert-dialog-radius: 1.25rem;
--cui-alert-dialog-inline-size: 30rem;
--cui-alert-dialog-border-color: #8b5cf6;
}
```
See [`api.yml`](api.yml) for the exhaustive inputs, callbacks, variables,
attributes, selectors, slots, and public interfaces.
## API reference
### Inputs
#### CAlertDialog server inputs
Server inputs are passed in a template through ` ` or in Python through
`CAlertDialog(...)`.
| Input | Type | Default | Effect |
|---|---|---|---|
| `id` | `str | None` | generated | Sets native Dialog identity and title, description, and activator relationships. |
| `open` | `bool` | `False` | Sets the server-visible initial modal state. |
| `close_on_escape` | `bool` | `True` | Permits Escape and platform cancel requests. |
| `size` | `"sm" | "md" | "lg"` ([`CAlertDialogSize`](#alert-dialog-interface-size)) | `"sm"` | Sets bounded decision-surface width. |
| `scroll` | `"body" | "dialog"` ([`CAlertDialogScroll`](#alert-dialog-interface-scroll)) | `"body"` | Chooses the overflow owner. |
| `class_` | `CClassValue | None` ([`CClassValue`](#alert-dialog-interface-class-value)) | `None` | Adds native Dialog classes. |
| `style` | `CStyleValue | None` ([`CStyleValue`](#alert-dialog-interface-style-value)) | `None` | Adds native Dialog inline styles. |
| `attrs` | `Mapping[str, object] | None` | `None` | Adds trusted native Dialog attributes without replacing owned modal semantics. |
#### CAlertDialog client inputs
Client inputs are passed in the browser through the `$c-props="{ ... }"` attribute on
` `.
| Input | Type | Omitted behavior | Effect |
|---|---|---|---|
| `open` | `boolean | null | undefined` | Releases control and preserves effective state. | Controls modal visibility while supplied as a Boolean. |
| `closeOnEscape` | `boolean | undefined` | Uses the server fallback. | Controls Escape and platform cancel behavior. |
| `size` | `"sm" | "md" | "lg" | undefined` | Uses the server fallback. | Controls `data-size` and width. |
| `scroll` | `"body" | "dialog" | undefined` | Uses the server fallback. | Controls `data-scroll` and overflow. |
| `onOpenChange` | `((open, detail) => void) | undefined` | No component callback. | Receives trigger, Escape, action, and native close requests. |
### Slots
Slots are passed as nested content or `` tags in a template, or through the
`slots={...}` argument in Python.
#### CAlertDialog slots
| Slot | Required | Data | Fallback |
|---|---|---|---|
| `activator` | no | `{activator_attrs, activator_type}` ([`CAlertDialogActivatorSlotData`](#alert-dialog-interface-activator-slot)) | No activator. |
| `title` | yes | `{}` ([`CAlertDialogTitleSlotData`](#alert-dialog-interface-title-slot)) | none |
| `description` | yes | `{}` ([`CAlertDialogDescriptionSlotData`](#alert-dialog-interface-description-slot)) | none |
| `default` | no | `{}` ([`CAlertDialogDefaultSlotData`](#alert-dialog-interface-default-slot)) | Supplemental body omitted. |
| `cancel` | yes | `{cancel_attrs, cancel_type}` ([`CAlertDialogCancelSlotData`](#alert-dialog-interface-cancel-slot)) | none |
| `action` | yes | `{action_attrs, action_type}` ([`CAlertDialogActionSlotData`](#alert-dialog-interface-action-slot)) | none |
### Events
Component events are callback inputs supplied through `$c-props`. Native browser events
remain available through Alpine `@...` attributes.
#### CAlertDialog events
| Event | Signature | Trigger and timing | Detail | Controlled and cancellation behavior |
|---|---|---|---|---|
| `onOpenChange` | `(requestedOpen: boolean, detail: CAlertDialogOpenChangeDetail) => void` ([`CAlertDialogOpenChangeDetail`](#alert-dialog-interface-open-change-detail)) | An owned trigger, Escape, explicit decision, or external native close requests a different visible state. | `{reason: "trigger" | "escape" | "action" | "native", controlled: boolean, source: Element | EventTarget | null, returnValue: string}` ([`CAlertDialogOpenChangeDetail`](#alert-dialog-interface-open-change-detail)) | Uncontrolled requests commit before notification. Controlled requests wait for the owner. Cancel and Action return `cancel` and `action` respectively. |
### Methods
-
### CSS
CSS variables to theme the components. Set them on an ancestor or the component itself.
#### CAlertDialog CSS variables
Apply these variables to `CAlertDialog` or one of its ancestors.
| Variable | Type | Purpose | Default |
|---|---|---|---|
| `--cui-alert-dialog-backdrop` | `color` | Modal backdrop. | `rgb(15 23 42 / 58%)` |
| `--cui-alert-dialog-background` | `color` | Surface background. | `Canvas` |
| `--cui-alert-dialog-foreground` | `color` | Surface text. | `CanvasText` |
| `--cui-alert-dialog-border-color` | `color` | Surface boundary. | `Subtle CanvasText mix.` |
| `--cui-alert-dialog-radius` | `length` | Surface radius. | `0.875rem` |
| `--cui-alert-dialog-shadow` | `shadow` | Surface elevation. | `0 1.5rem 4rem rgb(15 23 42 / 28%)` |
| `--cui-alert-dialog-inline-size` | `length` | Preferred responsive width. | `Size derived; 26rem at sm.` |
| `--cui-alert-dialog-max-block-size` | `length` | Maximum surface height. | `calc(100dvb - 2rem)` |
| `--cui-alert-dialog-padding` | `length` | Surface region padding. | `1.25rem` |
| `--cui-alert-dialog-gap` | `length` | Gap between regions. | `1rem` |
### Attributes
HTML attributes defined on the components that you can refer to for CSS, inspection, and
testing. Read-only.
#### CAlertDialog attributes
| Attribute | Element | Type | Meaning |
|---|---|---|---|
| `role` | Native AlertDialog | `alertdialog` | Exposes the urgent modal decision role. |
| `aria-modal` | Native AlertDialog | `true` | Matches native showModal modality. |
| `aria-labelledby` | Native AlertDialog | `IDREF` | References the required title. |
| `aria-describedby` | Native AlertDialog | `IDREF` | References the required alert message. |
| `data-open` | Native AlertDialog | `present-or-absent` | Mirrors effective native open state. |
| `data-size` | Native AlertDialog | `"sm" | "md" | "lg"` | Mirrors effective responsive size. |
| `data-scroll` | Native AlertDialog | `"body" | "dialog"` | Mirrors effective overflow mode. |
### Selectors
Selectors for the DOM nodes in the components that you can use for CSS, inspection, and
testing.
#### CAlertDialog selectors
| Selector | Element | Purpose |
|---|---|---|
| `[data-citry-ui-part="alert-dialog"]` | Native Dialog | Stable modal root and attrs destination. |
| `[data-citry-ui-part="surface"]` | Surface | Visual decision surface. |
| `[data-citry-ui-part="header"]` | Header | Title layout. |
| `[data-citry-ui-part="title"]` | Title | Required accessible name. |
| `[data-citry-ui-part="description"]` | Description | Required alert message. |
| `[data-citry-ui-part="body"]` | Body | Optional supplemental content. |
| `[data-citry-ui-part="actions"]` | Actions | Required Cancel and Action controls. |
### Interfaces
Aliases and data shapes referenced above.
#### Input type aliases
| Interface | Definition |
|---|---|
| `CClassValue` | `str | Mapping[str, bool] | Sequence[CClassValue]` |
| `CStyleValue` | `str | Mapping[str, str | int | float | bool | None] | Sequence[CStyleValue]` |
| `CAlertDialogSize` | `Literal["sm", "md", "lg"]` |
| `CAlertDialogScroll` | `Literal["body", "dialog"]` |
#### `CAlertDialogActivatorSlotData`
| Field | Type | Default | Meaning |
|---|---|---|---|
| `activator_attrs` | `dict[str, object]` | - | Owned trigger relationships and marker. |
| `activator_type` | `Literal["button"]` | - | Form-safe native Button type. |
#### `CAlertDialogTitleSlotData`
Empty dataclass: `{}`.
#### `CAlertDialogDescriptionSlotData`
Empty dataclass: `{}`.
#### `CAlertDialogDefaultSlotData`
Empty dataclass: `{}`.
#### `CAlertDialogCancelSlotData`
| Field | Type | Default | Meaning |
|---|---|---|---|
| `cancel_attrs` | `dict[str, object]` | - | Owned close marker, cancel return value, and autofocus. |
| `cancel_type` | `Literal["button"]` | - | Form-safe native Button type. |
#### `CAlertDialogActionSlotData`
| Field | Type | Default | Meaning |
|---|---|---|---|
| `action_attrs` | `dict[str, object]` | - | Owned close marker and action return value. |
| `action_type` | `Literal["button"]` | - | Form-safe native Button type. |
#### `CAlertDialogOpenChangeDetail`
| Field | Type | Default | Meaning |
|---|---|---|---|
| `reason` | `"trigger" | "escape" | "action" | "native"` | - | Request origin. |
| `controlled` | `boolean` | - | Whether client open currently owns state. |
| `source` | `Element | EventTarget | null` | - | Browser source associated with the request. |
| `returnValue` | `string` | - | cancel, action, or an empty string. |
### Translation keys
-
---
# CommandPalette
Source: https://citry.dev/ui-library/components/command-palette/
# CommandPalette
Use `CCommandPalette` for a finite collection of application commands that
people can search and run without leaving their current task. It combines a
native modal Dialog, one editable combobox, and grouped listbox options. The
application still owns command registration, authorization, routing, and side
effects.
## Open and run a command
Pass immutable records through `entries`, give the Dialog a visible `label`,
and handle values with `onAction`. The activator slot receives
`activator_attrs` and `activator_disabled`. Spread the complete attribute map
on one ordinary native activator. For `CButton`, also pass
`c-disabled="activator_disabled"` because Button owns its disabled state.
### Open and run a command
[Open the rendered preview](/ui-library/components/command-palette/_previews/basic-command-palette/)
````citry
from typing import Any
import citry_ui
from citry import Component, citry
from citry_ui import CCommandPaletteCommand
citry.register_library(citry_ui)
class BasicCommandPalette(Component):
def template_data(self, kwargs: Any, slots: Any) -> dict[str, object]: # noqa: ARG002
return {
"commands": (
CCommandPaletteCommand(
value="open-settings",
label="Open settings",
keywords=("preferences",),
shortcut="Ctrl ,",
),
CCommandPaletteCommand(value="create-project", label="Create project"),
CCommandPaletteCommand(value="invite-teammate", label="Invite teammate"),
)
}
template = """
Workspace commands
Search a small set of actions without leaving the current task.
lastAction=value,
onQueryChange:(value)=>lastQuery=value,
onOpenChange:(value)=>lastOpen=value ? 'open' : 'closed',
}"
>
Open command palette
State: closed ;
query: empty ;
action: none
"""
css = """
:where(.command-palette-basic) {
display: grid;
gap: 0.75rem;
justify-items: start;
color: CanvasText;
font-family: ui-sans-serif, system-ui, sans-serif;
}
:where(.command-palette-basic h2, .command-palette-basic p) { margin: 0; }
"""
preview = BasicCommandPalette()
preview # noqa: B018
````
Commands are callback-only options. They are not links, selected form values,
or Menu items. Use a native navigation list or Menu when people need link
semantics, modifier keys, a browser context menu, or copyable destinations.
## Build records in Python
`CCommandPaletteCommand`, `CCommandPaletteGroup`, and
`CCommandPaletteSeparator` are frozen value records. They do not render alone.
Command values stay globally unique across top-level entries and groups.
Separators are visual boundaries between top-level regions.
### Build command records in Python
[Open the rendered preview](/ui-library/components/command-palette/_previews/python-command-records/)
````citry
from typing import Any
import citry_ui
from citry import Component, citry
from citry_ui import (
CCommandPalette,
CCommandPaletteCommand,
CCommandPaletteGroup,
CCommandPaletteSeparator,
)
citry.register_library(citry_ui)
class PythonCommandRecords(Component):
def template_data(self, kwargs: Any, slots: Any) -> dict[str, object]: # noqa: ARG002
entries = (
CCommandPaletteGroup(
label="Project navigation",
commands=(
CCommandPaletteCommand(value="project-overview", label="Open project overview"),
CCommandPaletteCommand(value="project-files", label="Browse project files"),
),
),
CCommandPaletteSeparator(),
CCommandPaletteGroup(
label="Draft actions",
commands=(
CCommandPaletteCommand(value="save-draft", label="Save draft", shortcut="Ctrl S"),
CCommandPaletteCommand(
value="delete-draft",
label="Delete draft",
description="Moves this draft to Trash",
intent="danger",
),
),
),
)
return {
"python_palette": CCommandPalette(
label="Project commands",
entries=entries,
open=True,
)
}
template = """
Frozen Python records
The rendered palette preserves group, separator, and command order.
{{ python_palette }}
"""
css = """
:where(.command-palette-records) {
display: grid;
gap: 0.75rem;
color: CanvasText;
font-family: ui-sans-serif, system-ui, sans-serif;
}
:where(.command-palette-records h2, .command-palette-records p) { margin: 0; }
"""
preview = PythonCommandRecords()
preview # noqa: B018
````
## Search labels and aliases
Filtering normalizes labels, keywords, and the exact query with NFKC, collapses
Unicode whitespace, trims, and applies locale-neutral lowercase. A command
matches when the whole normalized query appears in its label or one keyword.
Descriptions, shortcut hints, values, and slot content are not searched.
Matches keep their server order and are never fuzzy-ranked.
### Search aliases and empty results
[Open the rendered preview](/ui-library/components/command-palette/_previews/search-and-empty/)
````citry
from typing import Any
import citry_ui
from citry import Component, citry
from citry_ui import CCommandPaletteCommand
citry.register_library(citry_ui)
class SearchAndEmpty(Component):
def template_data(self, kwargs: Any, slots: Any) -> dict[str, object]: # noqa: ARG002
return {
"commands": (
CCommandPaletteCommand(
value="theme",
label="Choose theme",
keywords=("appearance", "color mode"),
),
CCommandPaletteCommand(
value="light-mode",
label="Use light appearance",
keywords=("theme", "color mode"),
),
CCommandPaletteCommand(
value="dark-mode",
label="Use dark appearance",
keywords=("theme", "color mode"),
),
CCommandPaletteCommand(
value="managed-theme",
label="Use managed appearance",
keywords=("theme",),
disabled=True,
),
)
}
template = """
Exact substring search
Search appearance
Show no match
Show a disabled match
Clear search
open=value,
onQueryChange:(value)=>query=value,
}"
/>
Owner query: theme
"""
css = """
:where(.command-palette-search) {
display: grid;
gap: 0.75rem;
color: CanvasText;
font-family: ui-sans-serif, system-ui, sans-serif;
}
:where(.command-palette-search h2) { margin: 0; }
:where(.command-palette-search [role="group"]) {
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
}
"""
preview = SearchAndEmpty()
preview # noqa: B018
````
## Show disabled commands and shortcut hints
Disabled commands remain visible and searchable, expose disabled option state,
and are skipped by active navigation. `shortcut` is presentational text only.
The component never registers that key combination.
### Show disabled commands and shortcut hints
[Open the rendered preview](/ui-library/components/command-palette/_previews/disabled-and-shortcuts/)
````citry
from typing import Any
import citry_ui
from citry import Component, citry
from citry_ui import CCommandPaletteCommand
citry.register_library(citry_ui)
class DisabledAndShortcuts(Component):
def template_data(self, kwargs: Any, slots: Any) -> dict[str, object]: # noqa: ARG002
return {
"commands": (
CCommandPaletteCommand(
value="deploy-production",
label="Deploy production",
description="Unavailable until checks pass",
shortcut="Ctrl D",
disabled=True,
),
CCommandPaletteCommand(value="view-logs", label="View logs", shortcut="Ctrl L"),
CCommandPaletteCommand(
value="delete-environment",
label="Delete environment",
shortcut="Shift Delete",
intent="danger",
),
)
}
template = """
"""
css = """
:where(.command-palette-disabled) {
display: grid;
gap: 0.75rem;
color: CanvasText;
font-family: ui-sans-serif, system-ui, sans-serif;
}
:where(.command-palette-disabled h2) { margin: 0; }
:where(.command-palette-disabled [role="group"]) {
display: flex;
flex-wrap: wrap;
gap: 0.75rem;
}
@media (forced-colors: active) {
:where(.command-palette-disabled output) { border: 1px solid CanvasText; }
}
"""
preview = DisabledAndShortcuts()
preview # noqa: B018
````
Use `intent="danger"` to give a destructive command visual emphasis. It does
not authorize the action or bypass disabled state.
## Add safe visual adornments
The `item_start` and `item_end` slots receive immutable
`CCommandPaletteItemSlotData`. Their output is decorative, inert, and hidden
from the accessibility tree. Keep the owned label and description as the
command's complete semantic content. Interactive controls, links, meaningful
images, form controls, and custom elements are rejected.
### Add safe visual adornments
[Open the rendered preview](/ui-library/components/command-palette/_previews/command-adornments/)
````citry
from typing import Any
import citry_ui
from citry import Component, citry
from citry_ui import CCommandPaletteCommand
citry.register_library(citry_ui)
class CommandAdornments(Component):
def template_data(self, kwargs: Any, slots: Any) -> dict[str, object]: # noqa: ARG002
return {
"commands": (
CCommandPaletteCommand(
value="create-release",
label="Create release",
description="Prepare notes and artifacts",
keywords=("publish",),
),
CCommandPaletteCommand(
value="open-preview",
label="Open preview",
description="Inspect the latest deployment",
keywords=("beta",),
),
)
}
template = """
Release commands with decoration
open=value,
onAction:(value)=>last=value,
}"
>
◆
Beta
Action: none
"""
css = """
:where(.command-palette-adornments) {
display: grid;
gap: 0.75rem;
color: CanvasText;
font-family: ui-sans-serif, system-ui, sans-serif;
}
:where(.command-palette-adornments h2) { margin: 0; }
:where(.command-palette-adornments__badge) {
padding: 0.125rem 0.375rem;
border: 1px solid currentColor;
border-radius: 999px;
font-size: 0.6875rem;
}
:where(.command-palette-adornments__icon) { color: light-dark(#175cd3, #84adff); }
"""
preview = CommandAdornments()
preview # noqa: B018
````
## Control open state and query text
Client `open` and `query` values own independent axes while supplied. User
edits and dismissals are requests through `onQueryChange` and `onOpenChange`.
If the owner retains its old value, the input, results, active command, focus,
and Dialog remain on that accepted state.
### Control open state and query text
[Open the rendered preview](/ui-library/components/command-palette/_previews/controlled-command-palette/)
````citry
from typing import Any
import citry_ui
from citry import Component, citry
from citry_ui import CCommandPaletteCommand
citry.register_library(citry_ui)
class ControlledCommandPalette(Component):
def template_data(self, kwargs: Any, slots: Any) -> dict[str, object]: # noqa: ARG002
return {
"commands": (
CCommandPaletteCommand(value="workspace-alpha", label="Switch to Alpha workspace"),
CCommandPaletteCommand(value="workspace-bravo", label="Switch to Bravo workspace"),
CCommandPaletteCommand(value="workspace-charlie", label="Switch to Charlie workspace"),
)
}
template = """
"""
css = """
:where(.command-palette-controlled) {
display: grid;
gap: 0.75rem;
color: CanvasText;
font-family: ui-sans-serif, system-ui, sans-serif;
}
:where(.command-palette-controlled h2) { margin: 0; }
:where(.command-palette-controlled [role="group"]) {
display: flex;
flex-wrap: wrap;
gap: 0.625rem;
align-items: center;
}
"""
preview = ControlledCommandPalette()
preview # noqa: B018
````
A completed close clears the uncontrolled query exactly once. A declined
controlled close preserves it. Releasing a controlled value with `null` or by
omitting it continues from the last accepted fallback, never rejected browser
text or the original server seed.
## Choose action and close policy
`onAction(value, detail)` runs synchronously before an optional close request.
The root `close_on_action` default can be overridden by one command. Callback
return values are ignored. If the callback throws, the close step does not run.
If it deliberately moves focus, that connected focus destination wins over
Dialog return-focus behavior.
### Choose action and close policy
[Open the rendered preview](/ui-library/components/command-palette/_previews/command-actions/)
````citry
from typing import Any
import citry_ui
from citry import Component, citry
from citry_ui import CCommandPaletteCommand
citry.register_library(citry_ui)
class CommandActions(Component):
def template_data(self, kwargs: Any, slots: Any) -> dict[str, object]: # noqa: ARG002
return {
"commands": (
CCommandPaletteCommand(
value="copy-id",
label="Copy ID",
close_on_action=False,
),
CCommandPaletteCommand(
value="toggle-sidebar",
label="Toggle sidebar",
close_on_action=False,
),
CCommandPaletteCommand(
value="delete-draft",
label="Delete draft",
intent="danger",
),
)
}
template = """
"""
css = """
:where(.command-palette-actions) {
display: grid;
gap: 0.75rem;
justify-items: start;
color: CanvasText;
font-family: ui-sans-serif, system-ui, sans-serif;
}
:where(.command-palette-actions h2) { margin: 0; }
:where(.command-palette-actions [role="group"]) {
display: flex;
flex-wrap: wrap;
gap: 0.75rem;
}
"""
preview = CommandActions()
preview # noqa: B018
````
## Own global shortcuts in the application
CommandPalette installs no document or window shortcut listener. The
application decides how `Mod+K` behaves around editable controls, composition,
multiple palettes, operating-system reservations, and shortcut collisions,
then updates controlled `open`.
### Own a global shortcut in the application
[Open the rendered preview](/ui-library/components/command-palette/_previews/application-shortcut/)
````citry
from typing import Any
import citry_ui
from citry import Component, citry
from citry_ui import CCommandPaletteCommand
citry.register_library(citry_ui)
class ApplicationShortcut(Component):
def template_data(self, kwargs: Any, slots: Any) -> dict[str, object]: # noqa: ARG002
return {
"help_commands": (
CCommandPaletteCommand(value="help-docs", label="Open documentation"),
CCommandPaletteCommand(value="help-support", label="Contact support"),
),
"workspace_commands": (
CCommandPaletteCommand(value="workspace-settings", label="Open workspace settings"),
CCommandPaletteCommand(value="workspace-members", label="Manage workspace members"),
),
}
template = """
"""
css = """
:where(.command-palette-shortcut) {
display: grid;
gap: 0.75rem;
max-inline-size: 42rem;
color: CanvasText;
font-family: ui-sans-serif, system-ui, sans-serif;
}
:where(.command-palette-shortcut h2, .command-palette-shortcut p) { margin: 0; }
:where(.command-palette-shortcut [contenteditable]) {
min-block-size: 2.75rem;
padding: 0.625rem;
border: 1px solid currentColor;
}
"""
preview = ApplicationShortcut()
preview # noqa: B018
````
Shortcut text inside a command is a hint, not a binding or authorization rule.
## Keep Forms and IME input safe
The search input has no name, value contribution, reset behavior, or validity.
Every noncomposing Enter is contained before an ancestor Form can submit,
including empty and all-disabled results. During composition, Arrow, Enter,
and Escape remain with the IME and cannot navigate, act, clear, or dismiss.
The final committed text produces at most one query request.
### Keep Forms and IME input safe
[Open the rendered preview](/ui-library/components/command-palette/_previews/form-safe-palette/)
````citry
from typing import Any
import citry_ui
from citry import Component, citry
from citry_ui import CCommandPaletteCommand
citry.register_library(citry_ui)
class FormSafePalette(Component):
def template_data(self, kwargs: Any, slots: Any) -> dict[str, object]: # noqa: ARG002
return {
"commands": (
CCommandPaletteCommand(value="focus-name", label="Focus display name"),
CCommandPaletteCommand(value="submit-profile", label="Submit profile explicitly"),
CCommandPaletteCommand(value="managed-setting", label="Managed setting", disabled=True),
)
}
template = """
"""
css = """
:where(.command-palette-form) {
display: grid;
gap: 0.75rem;
justify-items: start;
color: CanvasText;
font-family: ui-sans-serif, system-ui, sans-serif;
}
:where(.command-palette-form h2, .command-palette-form p) { margin: 0; }
:where(.command-palette-form label) { display: grid; gap: 0.25rem; }
"""
preview = FormSafePalette()
preview # noqa: B018
````
An action callback may explicitly submit application data. The palette itself
never calls `requestSubmit()` or changes FormData.
## Compose with modal and anchored layers
CommandPalette uses the same native Dialog controller as `CDialog`. A nested
Dialog becomes the topmost focus owner. Popovers opened from a command close
before the palette. Escape closes only the deepest owned layer, and ordinary
close restores the eligible deep-focus invoker.
### Compose with modal and anchored layers
[Open the rendered preview](/ui-library/components/command-palette/_previews/palette-layers/)
````citry
from typing import Any
import citry_ui
from citry import Component, citry
from citry_ui import CCommandPaletteCommand
citry.register_library(citry_ui)
class PaletteLayers(Component):
def template_data(self, kwargs: Any, slots: Any) -> dict[str, object]: # noqa: ARG002
return {
"commands": (
CCommandPaletteCommand(
value="show-details",
label="Show deployment details",
close_on_action=False,
),
CCommandPaletteCommand(value="close-workflow", label="Finish workflow"),
)
}
template = """
Modal and anchored layers
Open deployment workflow
Deployment workflow
$store.commandPaletteLayers.paletteOpen=value,
onAction:(value)=>{
if (value==='show-details') $store.commandPaletteLayers.popoverOpen=true;
},
}"
>
Open workflow commands
$store.commandPaletteLayers.popoverOpen=value,
}"
>
Details anchor
Deployment details
The latest deployment passed its checks.
Remove palette owner
Palette owner present
"""
css = """
:where(.command-palette-layers) {
display: grid;
gap: 0.75rem;
justify-items: start;
color: CanvasText;
font-family: ui-sans-serif, system-ui, sans-serif;
}
:where(.command-palette-layers h2) { margin: 0; }
:where(.command-palette-layers__workflow) {
display: flex;
flex-wrap: wrap;
gap: 0.75rem;
align-items: center;
}
:where(.command-palette-layers__shadow-host) {
display: block;
padding: 0.75rem;
border: 1px solid currentColor;
}
"""
preview = PaletteLayers()
preview # noqa: B018
````
The Dialog stays in its authored Document or open ShadowRoot. Closed
ShadowRoots, cross-document adoption, invalid anatomy, and hostile ownership
changes fail closed.
## Adapt size, direction, and environment
`size` coordinates surface width, input height, and row density. Public
variables and part selectors support application styling. Logical layout keeps
start/end decoration correct in RTL, while vertical command order remains
unchanged.
### Inspect responsive and environment behavior
[Open the rendered preview](/ui-library/components/command-palette/_previews/command-palette-environment/)
````citry
from typing import Any
import citry_ui
from citry import Component, citry
from citry_ui import CCommandPaletteCommand, CCommandPaletteGroup
citry.register_library(citry_ui)
class CommandPaletteEnvironment(Component):
def template_data(self, kwargs: Any, slots: Any) -> dict[str, object]: # noqa: ARG002
return {
"commands": (
CCommandPaletteGroup(
label="Localized workspace administration",
commands=(
CCommandPaletteCommand(
value="archive-workspace",
label="Archive this exceptionally long localized workspace name",
description="Keeps a recoverable copy for organization administrators",
),
CCommandPaletteCommand(
value="delete-workspace",
label="Delete workspace permanently",
description="This command cannot be undone",
intent="danger",
),
CCommandPaletteCommand(
value="managed-workspace",
label="Transfer managed workspace",
disabled=True,
),
),
),
)
}
template = """
"""
css = """
:where(.command-palette-environment) {
display: grid;
gap: 0.75rem;
color: CanvasText;
color-scheme: light;
font-family: ui-sans-serif, system-ui, sans-serif;
}
:where(.command-palette-environment--dark) {
color-scheme: dark;
background: Canvas;
}
:where(.command-palette-environment h2, .command-palette-environment p) { margin: 0; }
:where(.command-palette-environment [role="group"]) {
display: flex;
flex-wrap: wrap;
gap: 0.75rem;
}
@media (forced-colors: active) {
:where(.command-palette-environment) { border: 1px solid CanvasText; }
}
@media print {
:where(.command-palette-environment [role="group"]) { display: none; }
}
"""
preview = CommandPaletteEnvironment()
preview # noqa: B018
````
The active option stays visible at narrow widths, 200% and 400% zoom, with a
virtual keyboard, coarse pointer, text spacing, reduced motion, and forced
colors. The modal palette is hidden in print.
Without JavaScript, a server-closed palette stays closed. A server-open native
Dialog remains readable in document flow without claiming modality. Its search
input remains disabled and commands do not run, so it cannot submit an
ancestor Form or promise unavailable interaction.
## Distinguish callbacks from native events
`onOpenChange`, `onQueryChange`, and `onAction` are component callbacks passed
through `$c-props`. Native input, composition, keyboard, pointer, click,
Dialog cancel, and close events remain browser events. The family dispatches no
custom DOM event.
`attrs` target the native Dialog. `input_attrs` target the owned search input
and accept only attributes that cannot replace its identity, value, disabled
state, Form boundary, combobox relationships, or active descendant. Mappings
are copied once. Labels, descriptions, keywords, shortcut hints, and values are
escaped text, not HTML or authorized domain actions.
## API reference
### Inputs
#### CCommandPalette server inputs
Server inputs are passed in a template through ` ` or in Python
through `CCommandPalette(...)`.
| Input | Type | Default | Effect |
|---|---|---|---|
| `entries` | `Sequence[CCommandPaletteEntry]` ([`CCommandPaletteEntry`](#command-palette-interface-command-palette-entry)) | required | Snapshots and validates ordered command, group, and separator records. |
| `label` | `non-whitespace str` | required | Supplies the visible Dialog title and accessible name. |
| `id` | `str | None` | generated | Sets the Dialog identity and bases owned relationship IDs. |
| `open` | `bool` | `False` | Selects initial server and uncontrolled Dialog visibility. |
| `query` | `str` | `""` | Seeds the exact search text without server-side filtering. |
| `disabled` | `bool` | `False` | Disables activation and force-closes an open palette. |
| `loop` | `bool` | `True` | Wraps active Arrow navigation at the first and last eligible command. |
| `close_on_action` | `bool` | `True` | Sets the root action-close default that each command may override. |
| `size` | `"sm" | "md" | "lg"` ([`CCommandPaletteSize`](#command-palette-interface-command-palette-size)) | `"md"` | Selects coordinated surface width and control density. |
| `placeholder` | `str` | `"Search commands"` | Supplies visible search-input placeholder text. |
| `search_label` | `non-whitespace str` | `"Search commands"` | Supplies the visually hidden native label for the search input. |
| `empty_label` | `non-whitespace str` | `"No commands found"` | Supplies the empty live-status fallback when the empty slot is omitted. |
| `close_label` | `non-whitespace str` | `"Close command palette"` | Supplies the built-in close Button accessible name. |
| `class_` | `CClassValue | None` ([`CClassValue`](#command-palette-interface-class-value)) | `None` | Adds classes to the native Dialog and merges them with attrs. |
| `style` | `CStyleValue | None` ([`CStyleValue`](#command-palette-interface-style-value)) | `None` | Adds styles to the native Dialog and merges them with attrs. |
| `attrs` | `Mapping[str, object] | None` | `None` | Adds copied allowed native Dialog attributes without replacing owned semantics or state. |
| `input_attrs` | `Mapping[str, object] | None` | `None` | Adds copied allowed search-input attributes without replacing Form, value, focus, or ARIA ownership. |
#### CCommandPalette client inputs
Client inputs are passed in the browser through the `$c-props="{ ... }"` attribute on
` `.
| Input | Type | Omitted behavior | Effect |
|---|---|---|---|
| `open` | `boolean | null` | Releases control from committed visibility; null has the same effect. | Controls native Dialog visibility while supplied as a Boolean. |
| `query` | `string | null` | Releases control from the last accepted internal query fallback; null has the same effect. | Controls exact input text and filtering while supplied as a string. |
| `disabled` | `boolean` | Uses the immutable server input. | Controls activation and forced closure. |
| `loop` | `boolean` | Uses the immutable server input. | Controls Arrow navigation wrapping. |
| `closeOnAction` | `boolean` | Uses the immutable server input. | Controls the root action-close default. |
| `size` | `"sm" | "md" | "lg"` ([`CCommandPaletteSize`](#command-palette-interface-command-palette-size)) | Uses the immutable server input. | Controls coordinated surface width and density. |
| `onOpenChange` | `function` | Omission selects no visibility callback; null clears the last valid callback. | Receives user-authored and forced visibility requests. |
| `onQueryChange` | `function` | Omission selects no query callback; null clears the last valid callback. | Receives committed user input and accepted-close reset requests. |
| `onAction` | `function` | Omission selects no command callback; null clears the last valid callback. | Receives one eligible command activation before optional close. |
### Slots
Slots are passed as nested content or `` tags in a template, or through the
`slots={...}` argument in Python.
#### CCommandPalette slots
| Slot | Required | Data | Fallback |
|---|---|---|---|
| `activator` | no | `{activator_attrs: dict[str, object], activator_disabled: bool}` | None. Bind the complete mapping to one ordinary native activator; for CButton also bind activator_disabled through disabled. |
| `item_start` | no | `CCommandPaletteItemSlotData` ([`CCommandPaletteItemSlotData`](#command-palette-interface-ccommand-palette-item-slot-data)) | None. Output is inert and accessibility-hidden visual decoration. |
| `item_end` | no | `CCommandPaletteItemSlotData` ([`CCommandPaletteItemSlotData`](#command-palette-interface-ccommand-palette-item-slot-data)) | Escaped shortcut text when supplied. Output is inert and accessibility-hidden. |
| `empty` | no | `{}` | Escaped empty_label text. Output is inert and cannot contain interactive content. |
### Events
Component events are callback inputs supplied through `$c-props`. Native browser events
remain available through Alpine `@...` attributes.
#### CCommandPalette events
| Event | Signature | Trigger and timing | Detail | Controlled and cancellation behavior |
|---|---|---|---|---|
| `onOpenChange` | `(requestedOpen: boolean, detail: CCommandPaletteOpenChangeDetail) => void` ([`CCommandPaletteOpenChangeDetail`](#command-palette-interface-ccommand-palette-open-change-detail)) | Activator, Escape, outside dismissal, close Button, action, native close, disabled transition, ancestor close, or owner request changes visibility. | `{reason, controlled, source}` ([`CCommandPaletteOpenChangeDetail`](#command-palette-interface-ccommand-palette-open-change-detail)) | Uncontrolled state commits before notification. Controlled state remains authoritative and may decline an ordinary close by retaining true. |
| `onQueryChange` | `(requestedQuery: string, detail: CCommandPaletteQueryChangeDetail) => void` ([`CCommandPaletteQueryChangeDetail`](#command-palette-interface-ccommand-palette-query-change-detail)) | A noncomposing user edit settles or an accepted close clears a nonempty query. | `{reason, closeReason, controlled, source}` ([`CCommandPaletteQueryChangeDetail`](#command-palette-interface-ccommand-palette-query-change-detail)) | Controlled input is request-only and restores every observable surface when the owner declines. Accepted close clears the internal fallback once. |
| `onAction` | `(value: string, detail: CCommandPaletteActionDetail) => void` ([`CCommandPaletteActionDetail`](#command-palette-interface-ccommand-palette-action-detail)) | An enabled visible active command receives unmodified Enter or an eligible option receives a plain click. | `{query, source, item, event, closeOnAction}` ([`CCommandPaletteActionDetail`](#command-palette-interface-ccommand-palette-action-detail)) | Runs synchronously before optional close; return values are ignored and an exception stops the close transaction. |
### Methods
-
### CSS
CSS variables to theme the components. Set them on an ancestor or the component itself.
#### CCommandPalette CSS variables
Apply these variables to `CCommandPalette` or one of its ancestors.
| Variable | Type | Purpose | Default |
|---|---|---|---|
| `--cui-command-palette-backdrop` | `color` | Native modal backdrop. | `Theme overlay color.` |
| `--cui-command-palette-background` | `color` | Dialog surface background. | `Theme surface color.` |
| `--cui-command-palette-foreground` | `color` | Primary text. | `Theme foreground.` |
| `--cui-command-palette-muted` | `color` | Descriptions and shortcut hints. | `Theme muted foreground.` |
| `--cui-command-palette-border-color` | `color` | Surface, input, and row boundaries. | `Theme border color.` |
| `--cui-command-palette-active-background` | `color` | Active option background. | `Theme subtle accent.` |
| `--cui-command-palette-active-foreground` | `color` | Active option text. | `Theme accent foreground.` |
| `--cui-command-palette-danger` | `color` | Danger command text. | `Theme danger color.` |
| `--cui-command-palette-radius` | `length` | Surface corner radius. | `0.875rem` |
| `--cui-command-palette-shadow` | `shadow` | Modal elevation. | `Theme overlay shadow.` |
| `--cui-command-palette-inline-size` | `length` | Preferred Dialog width. | `Size-derived.` |
| `--cui-command-palette-max-block-size` | `length` | Viewport-constrained Dialog height. | `calc(100dvb - 2rem)` |
| `--cui-command-palette-padding` | `length` | Outer surface spacing. | `0.75rem` |
| `--cui-command-palette-gap` | `length` | Gap between surface regions. | `0.5rem` |
| `--cui-command-palette-input-block-size` | `length` | Search-control height. | `Size-derived.` |
| `--cui-command-palette-row-min-block-size` | `length` | Command row minimum height. | `Size-derived; at least 2.75rem.` |
| `--cui-command-palette-row-padding-inline` | `length` | Command row horizontal inset. | `0.75rem` |
| `--cui-command-palette-group-gap` | `length` | Spacing between command groups. | `0.5rem` |
| `--cui-command-palette-focus-ring` | `color` | Visible keyboard focus ring. | `Theme focus color.` |
### Attributes
HTML attributes defined on the components that you can refer to for CSS, inspection, and
testing. Read-only.
#### CCommandPalette attributes
| Attribute | Element | Type | Meaning |
|---|---|---|---|
| `id` | Native Dialog | `supplied or generated string` | Identifies the palette and bases all owned relationships. |
| `open` | Native Dialog | `present | absent` | Native Dialog visibility; enhanced open uses showModal. |
| `data-open` | Native Dialog | `present | absent` | Mirrors effective committed visibility. |
| `data-disabled` | Native Dialog | `present | absent` | Mirrors effective palette disabledness. |
| `data-size` | Native Dialog | `"sm" | "md" | "lg"` ([`CCommandPaletteSize`](#command-palette-interface-command-palette-size)) | Mirrors effective surface width and density. |
| `data-empty` | Native Dialog | `present | absent` | Mirrors whether filtering exposes zero command results. |
| `aria-labelledby` | Native Dialog | `owned title IDREF` | Names the modal from its visible title. |
#### CCommandPalette attributes
| Attribute | Element | Type | Meaning |
|---|---|---|---|
| `type` | Search input | `"text"` | Avoids divergent native search Escape and clear behavior. |
| `role` | Search input | `"combobox"` | Exposes editable command filtering. |
| `aria-autocomplete` | Search input | `"list"` | Announces list filtering without completing the input value. |
| `aria-controls` | Search input | `owned listbox IDREF` | References the result collection. |
| `aria-expanded` | Search input | `"true" | "false"` | Mirrors effective result-surface visibility. |
| `aria-activedescendant` | Search input | `eligible owned option IDREF | absent` | Exposes internal active navigation while DOM focus stays in the input. |
| `disabled` | Search input | `present | absent` | Keeps server fallback and effective disabled state natively safe. |
#### CCommandPalette attributes
| Attribute | Element | Type | Meaning |
|---|---|---|---|
| `role` | Command row | `"option"` | Exposes one callback-only command candidate. |
| `aria-selected` | Command row | `"true" | "false"` | Mirrors transient active-descendant state rather than an application value. |
| `aria-disabled` | Command row | `"true" | absent` | Exposes an unavailable command. |
| `data-active` | Command row | `present | absent` | Mirrors internal active state. |
| `data-disabled` | Command row | `present | absent` | Mirrors immutable command disabledness. |
| `data-intent` | Command row | `"default" | "danger"` ([`CCommandPaletteIntent`](#command-palette-interface-command-palette-intent)) | Mirrors immutable visual intent. |
#### CCommandPalette attributes
| Attribute | Element | Type | Meaning |
|---|---|---|---|
| `role` | Command group | `"group"` | Groups commands under one visible label. |
| `aria-labelledby` | Command group | `owned group-label IDREF` | Names the group from its visible label. |
### Selectors
Selectors for the DOM nodes in the components that you can use for CSS, inspection, and
testing.
#### CCommandPalette selectors
| Selector | Element | Purpose |
|---|---|---|
| `[data-citry-ui-part="command-palette"]` | Native Dialog | Modal owner and class_, style, and attrs destination. |
| `[data-citry-ui-part="command-palette-surface"]` | Surface section | Contains every visual palette region. |
| `[data-citry-ui-part="command-palette-header"]` | Header | Lays out the title and close Button. |
| `[data-citry-ui-part="command-palette-title"]` | Heading | Provides visible Dialog name. |
| `[data-citry-ui-part="command-palette-close"]` | Button | Closes the current palette through shared Dialog policy. |
| `[data-citry-ui-part="command-palette-search"]` | Search landmark | Owns the native label and editable combobox. |
| `[data-citry-ui-part="command-palette-search-label"]` | Native label | Supplies the search input accessible name. |
| `[data-citry-ui-part="command-palette-input"]` | Text input | Owns query editing and active-descendant navigation. |
| `[data-citry-ui-part="command-palette-listbox"]` | Listbox | Owns visible command options and labelled groups. |
| `[data-citry-ui-part="command-palette-command"]` | Option row | Shows one callback-only command and its state. |
| `[data-citry-ui-part="command-palette-group"]` | Group section | Groups visible command options. |
| `[data-citry-ui-part="command-palette-group-label"]` | Group label | Names one visible group. |
| `[data-citry-ui-part="command-palette-separator"]` | Accessibility-hidden hr | Separates visible top-level regions. |
| `[data-citry-ui-part="command-palette-empty"]` | Live status | Announces and displays the empty result. |
| `[data-citry-ui-part="command-palette-item-start"]` | Inert leading wrapper | Displays accessibility-hidden visual decoration. |
| `[data-citry-ui-part="command-palette-item-end"]` | Inert trailing wrapper | Displays accessibility-hidden decoration or shortcut text. |
### Interfaces
Aliases and data shapes referenced above.
#### Input type aliases
| Interface | Definition |
|---|---|
| `CClassValue` | `str | Mapping[str, bool] | Sequence[CClassValue]` |
| `CStyleValue` | `str | Mapping[str, str | int | float | bool | None] | Sequence[CStyleValue]` |
| `CCommandPaletteEntry` | `CCommandPaletteCommand | CCommandPaletteGroup | CCommandPaletteSeparator` |
| `CCommandPaletteIntent` | `Literal["default", "danger"]` |
| `CCommandPaletteSize` | `Literal["sm", "md", "lg"]` |
| `CCommandPaletteActionSource` | `Literal["keyboard", "click"]` |
| `CCommandPaletteOpenReason` | `Literal["trigger", "escape", "outside", "close-button", "action", "native", "disabled", "ancestor", "owner"]` |
| `CCommandPaletteQueryReason` | `Literal["input", "close"]` |
#### `CCommandPaletteCommand`
| Field | Type | Default | Meaning |
|---|---|---|---|
| `value` | `non-whitespace str` | - | Globally unique opaque application command identity. |
| `label` | `non-whitespace str` | - | Visible owned command label and accessible name. |
| `description` | `str | None` | - | Optional visible owned supporting description. |
| `keywords` | `tuple[str, ...]` | - | Immutable search-only aliases; default is empty. |
| `shortcut` | `str | None` | - | Optional accessibility-hidden visual hint with no listener; default is null. |
| `disabled` | `bool` | - | Immutable unavailable state; default is false. |
| `close_on_action` | `bool | None` | - | Optional per-command close override; null uses the root policy. |
| `intent` | `CCommandPaletteIntent` ([`CCommandPaletteIntent`](#command-palette-interface-command-palette-intent)) | - | Visual default or danger emphasis; default is default. |
#### `CCommandPaletteGroup`
| Field | Type | Default | Meaning |
|---|---|---|---|
| `label` | `non-whitespace str` | - | Visible accessible group label. |
| `commands` | `tuple[CCommandPaletteCommand, ...]` | - | Nonempty immutable command tuple; groups never nest. |
#### `CCommandPaletteSeparator`
Empty dataclass: `{}`.
#### `CCommandPaletteItemSlotData`
| Field | Type | Default | Meaning |
|---|---|---|---|
| `value` | `str` | - | Stable command identity. |
| `label` | `str` | - | Owned command label. |
| `description` | `str | None` | - | Optional owned description. |
| `keywords` | `tuple[str, ...]` | - | Immutable search aliases. |
| `shortcut` | `str | None` | - | Optional visual shortcut hint. |
| `disabled` | `bool` | - | Immutable command disabledness. |
| `close_on_action` | `bool` | - | Effective command close policy after the root fallback. |
| `intent` | `CCommandPaletteIntent` ([`CCommandPaletteIntent`](#command-palette-interface-command-palette-intent)) | - | Immutable visual intent. |
#### `CCommandPaletteOpenChangeDetail`
| Field | Type | Default | Meaning |
|---|---|---|---|
| `reason` | `CCommandPaletteOpenReason` ([`CCommandPaletteOpenReason`](#command-palette-interface-command-palette-open-reason)) | - | Cause of the requested or committed visibility change. |
| `controlled` | `boolean` | - | Whether a valid client Boolean owns desired visibility. |
| `source` | `object | null` | - | Connected owned origin when one remains available. |
#### `CCommandPaletteQueryChangeDetail`
| Field | Type | Default | Meaning |
|---|---|---|---|
| `reason` | `CCommandPaletteQueryReason` ([`CCommandPaletteQueryReason`](#command-palette-interface-command-palette-query-reason)) | - | User input or accepted-close reset. |
| `closeReason` | `CCommandPaletteOpenReason | null` ([`CCommandPaletteOpenReason`](#command-palette-interface-command-palette-open-reason)) | - | Accepted close cause for reset; null for ordinary input. |
| `controlled` | `boolean` | - | Whether a valid client string owns effective query text. |
| `source` | `object | null` | - | Owned input or accepted close origin when available. |
#### `CCommandPaletteActionDetail`
| Field | Type | Default | Meaning |
|---|---|---|---|
| `query` | `string` | - | Exact accepted effective query at activation time. |
| `source` | `CCommandPaletteActionSource` ([`CCommandPaletteActionSource`](#command-palette-interface-command-palette-action-source)) | - | Keyboard Enter or accepted click-handler path. |
| `item` | `object` | - | Exact owned option Element. |
| `event` | `object` | - | Triggering native browser event. |
| `closeOnAction` | `boolean` | - | Effective close policy for this action. |
### Translation keys
Catalog keys used by this family. An explicit component input or slot listed in Override
takes precedence over the catalog for that instance.
#### CCommandPalette translation keys
| Key | Purpose | Variables | Override | Browser updates |
|---|---|---|---|---|
| `citry-ui-command-palette-placeholder` | Provides the search-field hint. | `None` | `placeholder` input | $c-tr updates `placeholder`. |
| `citry-ui-command-palette-search-label` | Labels the command search field. | `None` | `search_label` input | $c-tr updates text content. |
| `citry-ui-command-palette-empty` | Reports that no commands match. | `None` | `empty_label` input or `empty` slot | $c-tr updates fallback text. |
| `citry-ui-command-palette-close` | Names the palette close control. | `None` | `close_label` input | $c-tr updates `aria-label`. |
---
# ContextMenu
Source: https://citry.dev/ui-library/components/context-menu/
# ContextMenu
Use `CContextMenu` for application commands that belong to one target region.
It opens the existing Citry Menu at a trusted pointer point or at a visible
point derived from the focused target. It does not create a second Menu model.
Keep the browser's native context menu when copy, spelling, editing, links,
images, media, or embedded content are the primary job. The
[`contextmenu` event](https://w3c.github.io/uievents/#event-type-contextmenu),
the [browser event guide](https://developer.mozilla.org/en-US/docs/Web/API/Element/contextmenu_event),
and the [APG Menu pattern](https://www.w3.org/WAI/ARIA/apg/patterns/menubar/)
describe the platform contracts that ContextMenu joins. The safest native
browser path is to render no ContextMenu for content that mainly needs browser
commands.
## Start with a contextual action
Bind every value from the required `target` slot to exactly one direct native
Element. Make that target, or a useful descendant, focusable so keyboard users
can press the Context Menu key or Shift+F10.
```citry-html
Quarterly report.pdf
Rename
Duplicate
```
The target keeps its native semantics. ContextMenu does not add `role=button`,
`aria-expanded`, or Menu Button keys. A native focusable descendant is better
than adding an extra wrapper Tab stop when the content already has one.
### Start with a contextual action
[Open the rendered preview](/ui-library/components/context-menu/_previews/basic-context-menu/)
````citry
from typing import Any
import citry_ui
from citry import Component, citry
from citry_ui import CButton, CContextMenu, CMenuItem, CMenuSeparator
citry.register_library(citry_ui)
class BasicContextMenu(Component):
class Kwargs:
pass
class Slots:
pass
def template_data(
self,
kwargs: Kwargs, # noqa: ARG002
slots: Slots, # noqa: ARG002
) -> dict[str, Any]:
return {
"python_menu": CContextMenu(
aria_label="Invoice actions",
slots={
"target": lambda data: CButton(
variant="outline",
attrs=data.target_attrs,
slots={"default": "Invoices"},
),
"menu": (
CMenuItem(value="rename", slots={"default": "Rename"}),
CMenuItem(value="duplicate", slots={"default": "Duplicate"}),
CMenuSeparator(),
CMenuItem(
value="delete",
intent="danger",
slots={"default": "Delete"},
),
),
},
),
}
template = """
"""
js = """
$component(({ scope }) => {
scope.lastActionLabel = "Last action: No action yet";
scope.onAction = (value) => {
scope.lastActionLabel = `Last action: ${value}`;
};
});
"""
css = """
:where(.context-menu-basic) {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(min(100%, 16rem), 1fr));
gap: 1rem;
min-block-size: 20rem;
color: CanvasText;
font-family: ui-sans-serif, system-ui, sans-serif;
}
:where(.context-menu-basic article) {
display: grid;
gap: 0.75rem;
align-content: start;
}
:where(.context-menu-basic h3) {
margin: 0;
}
:where(.context-menu-basic__file) {
display: grid;
gap: 0.25rem;
padding: 1rem;
border: 1px solid color-mix(in srgb, CanvasText 20%, transparent);
border-radius: 0.75rem;
background: Canvas;
}
:where(.context-menu-basic__file:focus-visible) {
outline: 2px solid Highlight;
outline-offset: 2px;
}
:where(.context-menu-basic output) {
grid-column: 1 / -1;
}
"""
preview = BasicContextMenu()
preview # noqa: B018
````
## Keep one Menu model
The `menu` slot accepts the existing `CMenuItem`, `CMenuCheckboxItem`,
`CMenuRadioGroup`, `CMenuRadioItem`, `CMenuGroup`, `CMenuSeparator`, and
`CMenuSubmenu` declarations. Their values, choices, action ordering, item
callbacks, typeahead, submenu keys, links, disabled state, and validation are
the [Menu contract](/ui-library/components/menu/).
ContextMenu adds no item-model array or duplicate declaration API. Import Menu
declarations and `CMenuActionDetail` from their existing `citry_ui` exports.
### Keep one Menu model
[Open the rendered preview](/ui-library/components/context-menu/_previews/choices-and-submenus/)
````citry
import citry_ui
from citry import Component, citry
citry.register_library(citry_ui)
class ContextMenuChoicesAndSubmenus(Component):
template = """
"""
css = """
:where(.context-menu-choices) {
display: grid;
gap: 0.875rem;
justify-items: start;
min-block-size: 24rem;
max-inline-size: 22rem;
color: CanvasText;
font-family: ui-sans-serif, system-ui, sans-serif;
}
:where(.context-menu-choices h3) {
margin: 0;
}
:where(.context-menu-choices__card) {
display: grid;
gap: 0.25rem;
inline-size: min(18rem, 100%);
padding: 1rem;
border: 1px solid color-mix(in srgb, CanvasText 24%, transparent);
border-radius: 0.75rem;
background: color-mix(in srgb, Highlight 8%, Canvas);
}
:where(.context-menu-choices__card:focus-visible) {
outline: 2px solid Highlight;
outline-offset: 2px;
}
:where(.context-menu-choices__peer) {
padding: 0.75rem;
border: 1px dashed color-mix(in srgb, CanvasText 24%, transparent);
}
"""
preview = ContextMenuChoicesAndSubmenus()
preview # noqa: B018
````
## Own visibility without stealing native fallback
Supply client `open` and `onOpenChange` together when application state owns
visibility. A trusted closed-to-open request has to decide whether to suppress
the browser menu before its event listener returns. The callback therefore
uses a narrow claim protocol:
1. Set the owner's `open` state to `true` synchronously.
2. Return the literal Boolean `true` in the same callback turn.
Every other return, including a Promise or another truthy value, refuses that
opening. The candidate coordinates remain available in the detail, but the
component does not commit them or prevent the native default. Returning
`true` without supplying `open=true` on the next settled props turn is a broken
claim: the component stays closed and reports one diagnostic.
Return values are ignored for uncontrolled opening and for every close.
`open=null` or prop removal releases control from the currently committed
visibility, using the same handoff as CMenu.
### Own visibility without stealing native fallback
[Open the rendered preview](/ui-library/components/context-menu/_previews/controlled-open/)
````citry
import citry_ui
from citry import Component, citry
citry.register_library(citry_ui)
class ControlledContextMenu(Component):
template = """
"""
css = """
:where(.context-menu-controlled) {
display: grid;
gap: 1rem;
min-block-size: 22rem;
padding: 1rem;
color: CanvasText;
font-family: ui-sans-serif, system-ui, sans-serif;
}
:where(.context-menu-controlled__target) {
display: grid;
gap: 0.25rem;
padding: 1.25rem;
border-radius: 1rem;
background: light-dark(#eef4ff, #182230);
}
:where(.context-menu-controlled__target:focus-visible) {
outline: 2px solid Highlight;
outline-offset: 2px;
}
:where(.context-menu-controlled [role="group"]) {
display: flex;
flex-wrap: wrap;
gap: 0.625rem;
align-items: center;
}
:where(.context-menu-controlled label) {
display: inline-flex;
gap: 0.375rem;
align-items: center;
}
"""
preview = ControlledContextMenu()
preview # noqa: B018
````
## Keep browser commands
ContextMenu preserves native behavior for:
- input, textarea, select, option, and editable content;
- links with `href`, images, audio, video, object, embed, and iframe Elements;
- custom Elements and detectable open-shadow hosts;
- a noncollapsed Selection that intersects the target; and
- any composed event path containing `data-citry-context-menu-native`.
Put `data-citry-context-menu-native` on a standard host when outside code
cannot inspect a closed shadow. Shift plus secondary click is always a native
escape. Firefox may not dispatch `contextmenu` for that gesture, so
ContextMenu also recognizes the pointer sequence and never suppresses it.
If a custom Menu is already open, a protected native request closes it once
and leaves the platform default untouched. Citry never clears selected text.
### Keep browser commands
[Open the rendered preview](/ui-library/components/context-menu/_previews/native-content/)
````citry
import citry_ui
from citry import Component, citry
citry.register_library(citry_ui)
class ContextMenuNativeContent(Component):
template = """
"""
css = """
:where(.context-menu-native) {
display: grid;
gap: 1rem;
color: CanvasText;
font-family: ui-sans-serif, system-ui, sans-serif;
}
:where(.context-menu-native > p) {
max-inline-size: 62ch;
margin: 0;
}
:where(.context-menu-native__target) {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(min(100%, 14rem), 1fr));
gap: 0.75rem;
padding: 1rem;
border: 1px solid color-mix(in srgb, CanvasText 22%, transparent);
border-radius: 1rem;
}
:where(.context-menu-native__target > *) {
min-inline-size: 0;
padding: 0.625rem;
border-radius: 0.5rem;
background: color-mix(in srgb, Highlight 7%, Canvas);
}
:where(.context-menu-native__selection) {
user-select: text;
}
:where(.context-menu-native__eligible:focus-visible,
.context-menu-native__target:focus-visible) {
outline: 2px solid Highlight;
outline-offset: 2px;
}
:where(.context-menu-native iframe) {
inline-size: 100%;
min-block-size: 5rem;
border: 1px solid color-mix(in srgb, CanvasText 22%, transparent);
}
"""
preview = ContextMenuNativeContent()
preview # noqa: B018
````
## Bound touch and pen fallback
An eligible primary touch or pen press can request the Menu after 700 ms. The
hold cancels for movement beyond 10 CSS pixels, scrolling, selection, another
pointer, pointer end or cancellation, blur, visibility loss, disabledness, or
structural change. Citry does not cancel pointerdown, capture the pointer,
change `touch-action`, disable selection, or apply callout-suppression CSS.
An accepted synthetic hold suppresses only its matching trusted derived click,
through pointerup plus 1,500 ms and never beyond the 10-second absolute
deadline. This prevents one hold from also navigating, submitting, resetting,
or running a target click. Other clicks keep their native behavior.
Desktop emulation cannot prove an operating system's callout timing. If a
platform shows a native callout without a cancelable `contextmenu`, Citry does
not claim to suppress it. Test real touch and pen devices before release.
### Bound long press
[Open the rendered preview](/ui-library/components/context-menu/_previews/touch-and-pen/)
````citry
import citry_ui
from citry import Component, citry
citry.register_library(citry_ui)
class ContextMenuTouchAndPen(Component):
template = """
"""
css = """
:where(.context-menu-touch) {
display: grid;
gap: 1rem;
color: CanvasText;
font-family: ui-sans-serif, system-ui, sans-serif;
}
:where(.context-menu-touch__policy) {
display: grid;
gap: 0.25rem;
padding: 0.875rem;
border-inline-start: 0.25rem solid Highlight;
background: color-mix(in srgb, Highlight 8%, Canvas);
}
:where(.context-menu-touch__card) {
display: grid;
gap: 0.25rem;
inline-size: min(100%, 26rem);
padding: 1rem;
border: 1px solid color-mix(in srgb, CanvasText 22%, transparent);
border-radius: 0.75rem;
background: Canvas;
color: CanvasText;
text-align: start;
}
:where(.context-menu-touch__scroller) {
max-block-size: 8rem;
overflow: auto;
border: 1px solid color-mix(in srgb, CanvasText 16%, transparent);
}
:where(.context-menu-touch__spacer) {
min-block-size: 12rem;
padding: 1rem;
}
:where(.context-menu-touch__controls) {
display: flex;
flex-wrap: wrap;
gap: 0.75rem;
align-items: center;
}
:where(.context-menu-touch p,
.context-menu-touch ol) {
margin: 0;
}
"""
preview = ContextMenuTouchAndPen()
preview # noqa: B018
````
## Return focus deliberately
After an accepted request, focus moves to the first enabled Menu item. Escape
and a non-link command try the original deep focus snapshot, then the invoking
Element, then the focusable target. If those are unavailable, focus moves to
the nearest open modal Dialog or to the document body. A link action keeps
native navigation and skips focus return.
Outside pointer, focus outside, Tab, Shift+Tab, ancestor closure, and owner
focus movement do not restore focus. Owner-moved focus always wins.
### Return focus deliberately
[Open the rendered preview](/ui-library/components/context-menu/_previews/focus-and-keyboard/)
````citry
import citry_ui
from citry import Component, citry
citry.register_library(citry_ui)
class ContextMenuFocusAndKeyboard(Component):
template = """
"""
css = """
:where(.context-menu-focus) {
display: grid;
gap: 1rem;
min-block-size: 22rem;
color: CanvasText;
font-family: ui-sans-serif, system-ui, sans-serif;
}
:where(.context-menu-focus > p) {
max-inline-size: 62ch;
margin: 0;
}
:where(.context-menu-focus__row) {
display: flex;
flex-wrap: wrap;
gap: 0.75rem;
align-items: center;
justify-content: space-between;
padding: 1rem;
border: 1px solid color-mix(in srgb, CanvasText 22%, transparent);
border-radius: 0.75rem;
}
:where(.context-menu-focus__row > span:first-child) {
display: grid;
gap: 0.25rem;
}
:where(.context-menu-focus__row:focus-visible) {
outline: 2px solid Highlight;
outline-offset: 2px;
}
:where(.context-menu-focus__fallbacks) {
display: flex;
flex-wrap: wrap;
gap: 0.625rem;
}
:where(.context-menu-focus__modal-target) {
padding: 0.75rem;
border: 1px solid color-mix(in srgb, CanvasText 22%, transparent);
border-radius: 0.5rem;
background: Canvas;
color: CanvasText;
}
"""
preview = ContextMenuFocusAndKeyboard()
preview # noqa: B018
````
## Share the layer coordinator
Nested ContextMenus use the deepest bound target. A ContextMenu inside a
Popover, Tooltip, or Dialog keeps that logical ancestry. A coexisting ordinary
Menu shares the same coordinator; right-clicking inside its open surface stays
native and does not reinvoke the ContextMenu. A later modal outside the ancestry
force-closes it. Point and Menu surfaces remain inline in the same Document or
open ShadowRoot, then use native Popover for the top layer.
Events do not cross iframe Document boundaries. A child document needs its own
Citry installation and coordinator. An iframe Element inside the parent target
keeps its native context menu.
### Share the layer coordinator
[Open the rendered preview](/ui-library/components/context-menu/_previews/layers-and-roots/)
````citry
import citry_ui
from citry import Component, citry
citry.register_library(citry_ui)
class ContextMenuLayersAndRoots(Component):
template = """
"""
css = """
:where(.context-menu-layers) {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(min(100%, 18rem), 1fr));
gap: 1rem;
min-block-size: 28rem;
color: CanvasText;
font-family: ui-sans-serif, system-ui, sans-serif;
}
:where(.context-menu-layers article) {
display: grid;
gap: 0.75rem;
align-content: start;
min-inline-size: 0;
}
:where(.context-menu-layers h3) {
margin: 0;
}
:where(.context-menu-layers__outer,
.context-menu-layers__popover-target) {
display: grid;
gap: 0.75rem;
padding: 1rem;
border: 1px solid color-mix(in srgb, CanvasText 22%, transparent);
border-radius: 0.75rem;
}
:where(.context-menu-layers__inner) {
display: inline-block;
inline-size: fit-content;
padding: 0.375rem 0.625rem;
border-radius: 999px;
background: color-mix(in srgb, Highlight 14%, Canvas);
}
:where(.context-menu-layers__shadow-target) {
padding: 0.75rem;
border: 1px solid currentColor;
border-radius: 0.5rem;
background: Canvas;
color: CanvasText;
}
:where(.context-menu-layers iframe) {
inline-size: 100%;
min-block-size: 6rem;
}
:where(.context-menu-layers__diagnostics) {
grid-column: 1 / -1;
display: flex;
flex-wrap: wrap;
gap: 0.75rem;
align-items: center;
}
"""
preview = ContextMenuLayersAndRoots()
preview # noqa: B018
````
## Anchor to the accepted point
Pointer requests use the trusted event's viewport point. Keyboard and external
owner-open requests derive a visible logical start and block-end point from the
focused descendant or target. The Menu reads its own computed direction and
uses logical `bottom-start`, native CSS Anchor Positioning, and collision
fallbacks.
There is no public coordinate, target selector, Element reference, placement,
offset, or positioning-strategy input. A request whose target-derived rect is
fully outside the visual viewport is rejected rather than anchored to a stale
or arbitrary point.
### Anchor to the accepted point
[Open the rendered preview](/ui-library/components/context-menu/_previews/positioning-and-rtl/)
````citry
import citry_ui
from citry import Component, citry
citry.register_library(citry_ui)
class ContextMenuPositioningAndRtl(Component):
template = """
"""
css = """
:where(.context-menu-positioning) {
display: grid;
gap: 1rem;
color: CanvasText;
font-family: ui-sans-serif, system-ui, sans-serif;
}
:where(.context-menu-positioning__controls) {
display: flex;
flex-wrap: wrap;
gap: 0.625rem;
align-items: center;
}
:where(.context-menu-positioning__board) {
position: relative;
min-block-size: 20rem;
overflow: hidden;
border: 1px solid color-mix(in srgb, CanvasText 22%, transparent);
border-radius: 1rem;
background: linear-gradient(
135deg,
color-mix(in srgb, Highlight 10%, Canvas),
Canvas
);
}
:where(.context-menu-positioning__target) {
padding: 0.625rem;
border: 1px solid color-mix(in srgb, CanvasText 28%, transparent);
border-radius: 0.5rem;
background: Canvas;
}
:where(.context-menu-positioning__board .context-menu-positioning__target) {
position: absolute;
}
:where(.context-menu-positioning__target:focus-visible) {
outline: 2px solid Highlight;
outline-offset: 2px;
}
:where(.context-menu-positioning__target.is-top-start) {
inset-block-start: -0.25rem;
inset-inline-start: -0.25rem;
}
:where(.context-menu-positioning__target.is-top-end) {
inset-block-start: 0.5rem;
inset-inline-end: 0.5rem;
}
:where(.context-menu-positioning__target.is-bottom-start) {
inset-block-end: 0.5rem;
inset-inline-start: 0.5rem;
}
:where(.context-menu-positioning__target.is-bottom-end) {
inset-block-end: -0.25rem;
inset-inline-end: -0.25rem;
}
:where(.context-menu-positioning__target.is-offscreen) {
inset-block-start: -20rem;
inset-inline-start: -20rem;
}
:where(.context-menu-positioning__repair-scroller) {
max-block-size: 9rem;
overflow: auto;
border: 1px dashed color-mix(in srgb, CanvasText 24%, transparent);
}
:where(.context-menu-positioning__repair-spacer) {
min-block-size: 8rem;
padding: 0.5rem;
}
:where(.context-menu-positioning__transformed) {
inline-size: fit-content;
padding: 1rem;
filter: saturate(0.9);
transform: translateX(1rem);
contain: paint;
}
:where(.context-menu-positioning p) {
max-inline-size: 68ch;
margin: 0;
}
"""
preview = ContextMenuPositioningAndRtl()
preview # noqa: B018
````
## Use Menu styling and native fallback
`class_`, `style`, and `attrs` target the ContextMenu host. The host renders no
visual box, so set target presentation on the Element that binds
`target_attrs`. Existing `--cui-menu-*` variables inherit from the host or an
ancestor to the inline Menu surface. Existing Menu part selectors customize
its surface and items. ContextMenu adds no theme or coordinate variables.
### Use Menu styling and native fallback
[Open the rendered preview](/ui-library/components/context-menu/_previews/customization-and-fallback/)
````citry
import citry_ui
from citry import Component, citry
citry.register_library(citry_ui)
class ContextMenuCustomizationAndFallback(Component):
template = """
"""
css = """
:where(.context-menu-customization) {
display: grid;
gap: 1rem;
color: CanvasText;
font-family: ui-sans-serif, system-ui, sans-serif;
}
:where(.context-menu-customization__brands) {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(min(100%, 18rem), 1fr));
gap: 1rem;
}
:where(.context-menu-customization__controls) {
display: flex;
flex-wrap: wrap;
gap: 0.75rem;
align-items: center;
}
:where(.context-menu-customization article) {
display: grid;
gap: 0.75rem;
padding: 1rem;
border-radius: 1rem;
}
:where(.context-menu-customization h3,
.context-menu-customization p) {
margin: 0;
}
:where(.context-menu-customization__orchard) {
background: #f5f0df;
color: #203422;
--cui-menu-background: #fffdf5;
--cui-menu-foreground: #203422;
--cui-menu-border-color: #78916d;
}
:where(.context-menu-customization__harbor) {
background: #102b38;
color: #eefaff;
}
:where(.context-menu-customization__file) {
display: grid;
gap: 0.25rem;
padding: 1rem;
border: 1px solid currentColor;
border-radius: 0.75rem;
}
:where(.context-menu-customization__file:focus-visible) {
outline: 2px solid Highlight;
outline-offset: 2px;
}
.context-menu-customization
.brand-context-menu[data-citry-ui-part="context-menu"]
[data-citry-ui-part="menu"] {
border-width: 2px;
}
@media (forced-colors: active) {
:where(.context-menu-customization article) {
border: 1px solid CanvasText;
}
}
@media (prefers-reduced-motion: reduce) {
:where(.context-menu-customization) {
scroll-behavior: auto;
}
}
@media print {
:where(.context-menu-customization article) {
background: transparent;
color: black;
}
}
"""
preview = ContextMenuCustomizationAndFallback()
preview # noqa: B018
````
Without JavaScript, the target stays ordinary native content. A server-closed
Menu remains hidden through native Popover presence, while an initially open
Menu remains readable in document flow. Before successful initialization,
ContextMenu does not suppress native requests. Capability loss after
initialization closes the enhanced Menu before removing its point.
## Distinguish callbacks from native events
`onOpenChange` and `onAction` are component callbacks supplied through
`$c-props`. `onOpenChange` describes requests and forced closes;
`onAction` uses the existing `CMenuActionDetail`. ContextMenu dispatches no
custom DOM event.
Native events remain Alpine listeners in allowed `attrs` or target content.
The ContextMenu root has Citry's isolated expression scope, so an attrs listener
cannot read ancestor-local `x-data` identifiers directly. Use `$event`,
`$dispatch`, `$store`, or an explicit global bridge. Use component callbacks
for owner-local state.
The component owns `contextmenu`, ContextMenu/Shift+F10 keydown, and its
touch/pen pointer sequence. It does not stop propagation on those paths. Only
the exact trusted click derived from an accepted synthetic long press is
prevented and stopped immediately so the target's primary action cannot also
run.
## Keep target and host attributes separate
`target_attrs` is copied, validated, and included in the target slot data. It
may carry ordinary classes, styles, safe ARIA, semantic native attributes,
nonreserved data, and unrelated native listeners. It cannot author the target
ID, ContextMenu marker, owned invocation events, `role`, native `disabled`,
Popover/anchor state, or Menu Button ARIA.
Host `attrs` accepts ordinary descriptive attributes, `dir`, `lang`,
nonreserved data, and unrelated native listeners. It cannot replace owned
identity, roles, ARIA, parts, reflections, lifecycle, Popover/anchor state, or
Citry runtime namespaces. Mappings are copied once. ContextMenu trusts ordinary
slot content as application content; it is not an HTML or URL sanitizer.
CContextMenu is not Form-associated. It emits no name/value pair and does not
participate in reset or constraint validation. Target descendants keep their
native Form behavior. A target Button may submit or reset on primary
activation; only the exact derived click from an accepted synthetic long press
is suppressed.
## API reference
### Inputs
#### CContextMenu server inputs
Server inputs are passed in a template through ` ` or in Python through
`CContextMenu(...)`.
| Input | Type | Default | Effect |
|---|---|---|---|
| `id` | `str | None` | generated | Sets the correlated host, target, point, Menu surface, declaration, and submenu ID family. |
| `aria_label` | `non-whitespace str` | required | Supplies the root Menu surface accessible name without relabelling the arbitrary target. |
| `open` | `bool` | `False` | Sets server and uncontrolled Menu visibility. |
| `disabled` | `bool` | `False` | Blocks custom invocation and force-closes an open Menu while preserving native context behavior. |
| `loop` | `bool` | `True` | Selects existing CMenu arrow and typeahead wrapping. |
| `close_on_select` | `bool` | `True` | Selects the existing root CMenu action-close policy. |
| `size` | `"sm" | "md" | "lg"` ([`CMenuSize`](#context-menu-interface-menu-size)) | `"md"` | Selects existing CMenu item geometry. |
| `class_` | `CClassValue | None` ([`CClassValue`](#context-menu-interface-class-value)) | `None` | Adds host classes and merges them with attrs. |
| `style` | `CStyleValue | None` ([`CStyleValue`](#context-menu-interface-style-value)) | `None` | Adds host styles; inherited Menu variables reach the inline surface. |
| `attrs` | `Mapping[str, object] | None` | `None` | Adds copied allowed host attributes and isolated-scope unrelated native listeners. |
| `target_attrs` | `Mapping[str, object] | None` | `None` | Adds copied allowed attributes to the exact bound target mapping without replacing owned identity or invocation behavior. |
#### CContextMenu client inputs
Client inputs are passed in the browser through the `$c-props="{ ... }"` attribute on
` `.
| Input | Type | Omitted behavior | Effect |
|---|---|---|---|
| `open` | `boolean | null` | Releases control from the current committed visibility; null has the same effect. | Controls Menu visibility while supplied as a Boolean. |
| `disabled` | `boolean` | Uses the server input. | Controls custom invocation disabledness; native target and fieldset disabledness remain authoritative. |
| `loop` | `boolean` | Uses the server input. | Controls existing CMenu navigation wrapping. |
| `closeOnSelect` | `boolean` | Uses the server input. | Controls the existing root CMenu action-close policy. |
| `size` | `"sm" | "md" | "lg"` ([`CMenuSize`](#context-menu-interface-menu-size)) | Uses the server input. | Controls existing CMenu item geometry. |
| `onOpenChange` | `function` | Omission or null selects no visibility callback and refuses a controlled native-default claim. | Receives visibility requests and forced closes; only a synchronous literal-true return can claim a controlled closed-to-open native request. |
| `onAction` | `function` | Omission or null selects no root action callback. | Receives existing valued CMenu command and choice activations. |
### Slots
Slots are passed as nested content or `` tags in a template, or through the
`slots={...}` argument in Python.
#### CContextMenu slots
| Slot | Required | Data | Fallback |
|---|---|---|---|
| `target` | yes | `{target_attrs}` ([`CContextMenuTargetSlotData`](#context-menu-interface-ccontext-menu-target-slot-data)) | None. Must settle to exactly one direct standard native Element with the complete target_attrs mapping bound. |
| `menu` | yes | `{}` ([`CContextMenuMenuSlotData`](#context-menu-interface-ccontext-menu-menu-slot-data)) | None. Requires one or more direct existing CMenu declarations under the existing collection rules. |
### Events
Component events are callback inputs supplied through `$c-props`. Native browser events
remain available through Alpine `@...` attributes.
#### CContextMenu events
| Event | Signature | Trigger and timing | Detail | Controlled and cancellation behavior |
|---|---|---|---|---|
| `onOpenChange` | `(requestedOpen: boolean, detail: CContextMenuOpenChangeDetail) => boolean | void` ([`CContextMenuOpenChangeDetail`](#context-menu-interface-ccontext-menu-open-change-detail)) | A trusted contextual request, Menu dismissal, or forced native, disabled, or ancestor close occurs. | `{reason, controlled, forced, source, clientX, clientY}` ([`CContextMenuOpenChangeDetail`](#context-menu-interface-ccontext-menu-open-change-detail)) | Candidate coordinates accompany refused controlled requests. A controlled closed-to-open request is claimed only when the callback synchronously sets owner open state and returns literal true; all other returns refuse without preventing the native default. |
| `onAction` | `(value: string, detail: CMenuActionDetail) => void` ([`CMenuActionDetail`](#context-menu-interface-ccontext-menu-action-detail)) | An enabled valued CMenu command, checkbox, or radio activates. | `{kind, item, event, path}` ([`CMenuActionDetail`](#context-menu-interface-ccontext-menu-action-detail)) | Uses exact CMenu callback order and action detail. ContextMenu adds no target or point fields to action detail. |
### Methods
-
### CSS
CSS variables to theme the components. Set them on an ancestor or the component itself.
#### CContextMenu CSS variables
Apply these variables to `CContextMenu` or one of its ancestors.
| Variable | Type | Purpose | Default |
|---|---|---|---|
| `--cui-menu-background` | `color` | Root and submenu Menu surfaces. | `Canvas` |
| `--cui-menu-foreground` | `color` | Menu item text. | `CanvasText` |
| `--cui-menu-muted-color` | `color` | Descriptions, labels, and shortcuts. | `color-mix(in srgb, current foreground 72%, transparent)` |
| `--cui-menu-border-color` | `color` | Menu surface and separator boundaries. | `color-mix(in srgb, CanvasText 18%, transparent)` |
| `--cui-menu-border-width` | `length` | Menu surface boundary width. | `1px` |
| `--cui-menu-radius` | `length` | Menu surface corners. | `0.75rem` |
| `--cui-menu-shadow` | `shadow` | Root Menu elevation. | `0 0.75rem 2rem rgb(15 23 42 / 18%)` |
| `--cui-menu-submenu-shadow` | `shadow` | Nested Menu elevation. | `0 1rem 2.5rem rgb(15 23 42 / 22%)` |
| `--cui-menu-inline-size` | `length` | Preferred Menu width. | `14rem` |
| `--cui-menu-min-inline-size` | `length` | Minimum useful submenu corridor. | `10rem` |
| `--cui-menu-max-inline-size` | `length` | Viewport-safe Menu width. | `calc(100dvi - 1rem)` |
| `--cui-menu-max-block-size` | `length` | Menu scroll limit. | `min(24rem, calc(100dvb - 1rem))` |
| `--cui-menu-padding` | `length` | Menu surface edge spacing. | `0.375rem` |
| `--cui-menu-item-block-size` | `length` | Menu item minimum height. | `Size-derived.` |
| `--cui-menu-item-padding-inline` | `length` | Menu item inline spacing. | `Size-derived.` |
| `--cui-menu-item-gap` | `length` | Menu item-region gap. | `0.625rem` |
| `--cui-menu-item-radius` | `length` | Menu item corners. | `0.5rem` |
| `--cui-menu-hover-background` | `color` | Enabled pointer-hover fill. | `color-mix(in srgb, CanvasText 8%, transparent)` |
| `--cui-menu-focus-background` | `color` | Focused Menu item fill. | `light-dark(#175cd3, #84adff)` |
| `--cui-menu-focus-foreground` | `color` | Focused Menu item content. | `light-dark(#ffffff, #101828)` |
| `--cui-menu-focus-outline-color` | `color` | Menu item focus-visible outline. | `light-dark(#175cd3, #84adff)` |
| `--cui-menu-danger-color` | `color` | Destructive Menu item content. | `light-dark(#b42318, #fda29b)` |
| `--cui-menu-disabled-opacity` | `number` | Disabled Menu content opacity. | `0.5` |
| `--cui-menu-offset` | `length` | Context point to root Menu gap. | `0.375rem` |
| `--cui-menu-submenu-offset` | `length` | Nested Menu anchor gap. | `0.25rem` |
| `--cui-menu-duration` | `time` | Menu entry and exit duration. | `120ms` |
| `--cui-menu-easing` | `easing` | Menu entry and exit curve. | `cubic-bezier(0.2, 0.8, 0.2, 1)` |
### Attributes
HTML attributes defined on the components that you can refer to for CSS, inspection, and
testing. Read-only.
#### CContextMenu attributes
| Attribute | Element | Type | Meaning |
|---|---|---|---|
| `id` | ContextMenu host | `supplied or generated string` | Identifies the host and bases the correlated target, point, surface, and declaration IDs. |
| `data-open` | ContextMenu host | `present | absent` | Mirrors committed root Menu visibility. |
| `data-disabled` | ContextMenu host | `present | absent` | Mirrors effective component, native target, and fieldset disabledness. |
| `data-size` | ContextMenu host | `"sm" | "md" | "lg"` ([`CMenuSize`](#context-menu-interface-menu-size)) | Mirrors effective CMenu item geometry. |
| `data-invocation` | ContextMenu host | `"pointer" | "keyboard" | "long-press" | "external" | absent` | Identifies the latest accepted invocation only while the Menu is open. |
#### CContextMenu attributes
| Attribute | Element | Type | Meaning |
|---|---|---|---|
| `id` | Bound target Element | `root ID plus -target` | Uses the exact owned target identity from target_attrs. |
| `data-citry-context-menu-native` | Target or descendant | `present | absent` | Preserves the browser context menu for a consumer-declared native path. |
#### CContextMenu attributes
| Attribute | Element | Type | Meaning |
|---|---|---|---|
| `id` | Root Menu surface | `root ID plus -menu` | Uses the exact owned root Menu identity. |
| `popover` | Root and submenu Menu surfaces | `"manual"` | Uses native top-layer presence with Citry dismissal. |
| `role` | Root and submenu Menu surfaces | `"menu"` | Exposes application Menu semantics. |
| `aria-label` | Root Menu surface | `non-whitespace string` | Uses required aria_label without naming the arbitrary target as a Menu Button. |
| `data-open` | Root and submenu Menu surfaces | `present | absent` | Mirrors logical Menu visibility. |
| `data-placement` | Root Menu surface | `"bottom-start"` | Mirrors the fixed requested logical point placement rather than collision result. |
| `data-size` | Root Menu surface | `"sm" | "md" | "lg"` ([`CMenuSize`](#context-menu-interface-menu-size)) | Mirrors effective Menu item geometry. |
#### CContextMenu attributes
| Attribute | Element | Type | Meaning |
|---|---|---|---|
| `role` | Menu item, group, separator, submenu trigger, or submenu surface | `CMenu-owned role` | Uses existing CMenu command, choice, group, separator, and submenu semantics. |
| `aria-labelledby` | Menu item or labelled group | `owned label IDREF | absent` | Uses existing CMenu exact visible labels. |
| `aria-describedby` | Menu item root | `description IDREF | absent` | Uses an optional separate CMenu item description. |
| `aria-disabled` | Menu item root | `"true" | absent` | Represents a focusable inactive CMenu item. |
| `data-disabled` | Menu item root | `present | absent` | Mirrors effective CMenu item disabledness. |
| `data-intent` | Menu item root | `"default" | "danger"` | Mirrors existing CMenu item emphasis. |
| `aria-checked` | Checkbox or radio item | `"false" | "true" | "mixed"` | Uses effective CMenu choice state; radio items never use mixed. |
| `data-checked` | Checkbox or radio item | `"false" | "true" | "mixed"` | Mirrors effective CMenu choice state. |
| `aria-haspopup` | Submenu trigger | `"menu"` | Announces an existing CMenu child surface. |
| `aria-controls` | Submenu trigger | `child Menu IDREF` | References the existing child Menu surface. |
| `aria-expanded` | Submenu trigger | `"true" | "false"` | Mirrors existing child Menu visibility. |
### Selectors
Selectors for the DOM nodes in the components that you can use for CSS, inspection, and
testing.
#### CContextMenu selectors
| Selector | Element | Purpose |
|---|---|---|
| `[data-citry-ui-part="context-menu"]` | Host div | Lifecycle owner and class_, style, and attrs destination; it has no visual box. |
| `[data-citry-ui-part="menu"]` | Root or submenu Menu surface | Existing CMenu Popover presence and collection focus. |
| `[data-citry-ui-part="menu-item"]` | Command, link, checkbox, or radio root | Existing CMenu item styling. |
| `[data-citry-ui-part="menu-item-start"]` | Decorative item wrapper | Existing logical-start content. |
| `[data-citry-ui-part="menu-item-label"]` | Visible item label | Existing layout and exact accessible-name target. |
| `[data-citry-ui-part="menu-item-description"]` | Optional item description | Existing supporting text and accessible description. |
| `[data-citry-ui-part="menu-item-end"]` | Decorative item wrapper | Existing shortcut or logical-end content. |
| `[data-citry-ui-part="menu-choice-indicator"]` | Decorative choice marker | Existing checkbox and radio state. |
| `[data-citry-ui-part="menu-group"]` | Labelled group root | Existing grouped-command layout. |
| `[data-citry-ui-part="menu-group-label"]` | Visible group label | Existing exact group name and layout. |
| `[data-citry-ui-part="menu-radio-group"]` | Radio-group root | Existing exclusive choice grouping. |
| `[data-citry-ui-part="menu-separator"]` | Horizontal separator | Existing collection division. |
| `[data-citry-ui-part="menu-submenu"]` | Neutral submenu wrapper | Existing child trigger and surface ownership. |
| `[data-citry-ui-part="menu-submenu-trigger"]` | Submenu Button | Existing child Menu activation and placement anchor. |
### Interfaces
Aliases and data shapes referenced above.
#### Input type aliases
| Interface | Definition |
|---|---|
| `CClassValue` | `str | Mapping[str, bool] | Sequence[CClassValue]` |
| `CStyleValue` | `str | Mapping[str, str | int | float | bool | None] | Sequence[CStyleValue]` |
| `CMenuSize` | `Literal["sm", "md", "lg"]` |
#### `CContextMenuTargetSlotData`
| Field | Type | Default | Meaning |
|---|---|---|---|
| `target_attrs` | `dict[str, object]` | - | Generated target ID, private ownership marker, and the validated copied target_attrs mapping. |
#### `CContextMenuMenuSlotData`
Empty dataclass: `{}`.
#### `CContextMenuOpenChangeDetail`
| Field | Type | Default | Meaning |
|---|---|---|---|
| `reason` | `"contextmenu" | "keyboard" | "long-press" | "escape" | "outside" | "focus-outside" | "tab" | "action" | "native" | "disabled" | "ancestor"` | - | Cause of the requested or forced visibility change. |
| `controlled` | `boolean` | - | Whether a valid client Boolean owns desired Menu visibility. |
| `forced` | `boolean` | - | Whether native or structural safety overrides owner refusal. |
| `source` | `Element | EventTarget | null` | - | Responsible composed-path target, focused Element, item, surface, target, or ancestor when still connected. |
| `clientX` | `float` | - | Candidate or latest committed visual-viewport-clamped x coordinate. |
| `clientY` | `float` | - | Candidate or latest committed visual-viewport-clamped y coordinate. |
#### `CMenuActionDetail`
| Field | Type | Default | Meaning |
|---|---|---|---|
| `kind` | `"command" | "checkbox" | "radio"` | - | Activated semantic CMenu item kind. |
| `item` | `Element` | - | Activated CMenu item root. |
| `event` | `Event` | - | Native CMenu activation event. |
| `path` | `list[str]` | - | Canonical ancestor-submenu path from the ContextMenu root Menu. |
### Translation keys
-
---
# Dialog
Source: https://citry.dev/ui-library/components/dialog/
# Dialog
Use `CDialog` for a task or decision that temporarily blocks the page. It
renders a native ``, enters the browser top layer, makes background
content inert, contains focus, restores focus, and locks page scrolling.
## Dialog at a glance
Use `sm` for one clear decision, `md` for ordinary tasks, `lg` for richer
content, and `full` when the task needs the viewport.
### Dialog at a glance
[Open the rendered preview](/ui-library/components/dialog/_previews/at-a-glance/)
````citry
import citry_ui
from citry import Component, citry
citry.register_library(citry_ui)
class DialogAtAGlance(Component):
template = """
Lunar atlas
Mare Imbrium
A compact note for one clear decision.
Open field note
Mare Imbrium
The basin spans more than 1,100 kilometres.
Deep-sky catalog
Orion Nebula
A generous surface for richer observations.
Inspect nebula
Orion Nebula
A stellar nursery visible below Orion's belt.
New stars illuminate clouds of hydrogen, dust, and ionized gas.
"""
css = """
:where(.dialog-glance) {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(min(100%, 18rem), 1fr));
gap: 1rem;
max-width: 64rem;
color: CanvasText;
font-family: ui-sans-serif, system-ui, sans-serif;
}
:where(.dialog-glance article) {
display: grid;
gap: 0.75rem;
align-content: start;
padding: 1.25rem;
border: 1px solid light-dark(#c4b5fd, #6d28d9);
border-radius: 0.875rem;
background: Canvas;
box-shadow: 0 0.75rem 2rem rgb(15 23 42 / 10%);
}
:where(.dialog-glance h2, .dialog-glance p) {
margin: 0;
}
:where(.dialog-glance__eyebrow) {
color: light-dark(#6d28d9, #c4b5fd);
font-size: 0.75rem;
font-weight: 700;
letter-spacing: 0.08em;
text-transform: uppercase;
}
"""
preview = DialogAtAGlance()
preview # noqa: B018
````
## Build a Dialog
Provide a required title and body. Spread `activator_attrs` onto the control
that opens it. Spread `close_attrs` onto explicit completion or cancel actions.
### Open a field note
[Open the rendered preview](/ui-library/components/dialog/_previews/open-field-note/)
````citry
import citry_ui
from citry import Component, citry
citry.register_library(citry_ui)
class OpenFieldNote(Component):
template = """
Tonight's observation
Aurora over the northern ridge
Read field note
Aurora over the northern ridge
Recorded at 01:42 under a clear sky.
Green ribbons appeared low on the horizon, then climbed toward
the zenith in three bright arcs.
Close note
Add to atlas
"""
css = """
:where(.field-note) {
display: grid;
gap: 0.75rem;
justify-items: start;
max-width: 42rem;
padding: 1.25rem;
border: 1px solid light-dark(#bae6fd, #0369a1);
border-radius: 0.875rem;
background: Canvas;
color: CanvasText;
font-family: ui-sans-serif, system-ui, sans-serif;
}
:where(.field-note h2, .field-note p) {
margin: 0;
}
:where(.field-note > p) {
color: light-dark(#0369a1, #7dd3fc);
font-size: 0.75rem;
font-weight: 700;
letter-spacing: 0.08em;
text-transform: uppercase;
}
"""
preview = OpenFieldNote()
preview # noqa: B018
````
```citry-html
Read field note
Aurora over the northern ridge
Recorded at 01:42 under a clear sky.
...
Close note
```
Compose a Dialog in Python when its content is already available there:
```python
from citry_ui import CDialog
field_note = CDialog(
slots={
"title": "Aurora over the northern ridge",
"default": note_content,
},
)
```
The title becomes the accessible name. Use `description` for one concise
summary. Keep structured or lengthy content in the body so assistive technology
does not announce it as one uninterrupted description.
The activator is optional. A controlled owner may open the Dialog without one.
## Configure Dialog
Server inputs are passed in Python through ` ` attributes or a
`CDialog(...)` composition call. Client inputs are passed in the browser
through `$c-props="{...}"`.
### Configure Dialog
[Open the rendered preview](/ui-library/components/dialog/_previews/configuration/)
````citry
import citry_ui
from citry import Component, citry
citry.register_library(citry_ui)
class ConfigureDialog(Component):
template = """
Observation archive
Configure the Dialog
Preview configuration
Observation archive
Test size, scrolling, and passive dismissal.
The archive currently holds 384 lunar observations.
Try Escape, the backdrop, and the explicit action.
Finish preview
"""
css = """
:where(.dialog-config) {
display: grid;
gap: 0.75rem;
justify-items: start;
max-width: 52rem;
padding: 1.25rem;
border: 1px solid light-dark(#c4b5fd, #6d28d9);
border-radius: 0.875rem;
background: Canvas;
color: CanvasText;
font-family: ui-sans-serif, system-ui, sans-serif;
}
:where(.dialog-config h2, .dialog-config p) {
margin: 0;
}
:where(.dialog-config > p) {
color: light-dark(#6d28d9, #c4b5fd);
font-size: 0.75rem;
font-weight: 700;
letter-spacing: 0.08em;
text-transform: uppercase;
}
"""
preview_controls = (
{
"name": "size",
"label": "Size",
"type": "select",
"default": "md",
"options": (("sm", "Small"), ("md", "Medium"), ("lg", "Large"), ("full", "Full")),
},
{
"name": "scroll",
"label": "Scroll",
"type": "select",
"default": "body",
"options": (("body", "Body only"), ("dialog", "Complete Dialog")),
},
{
"name": "dismissible",
"label": "Allow passive dismissal",
"type": "checkbox",
"default": True,
},
{
"name": "close_on_escape",
"label": "Close on Escape",
"type": "checkbox",
"default": True,
},
{
"name": "close_on_outside",
"label": "Close on backdrop press",
"type": "checkbox",
"default": True,
},
)
preview = ConfigureDialog()
preview # noqa: B018
````
A valid client input wins over its server value. Removing it restores the
server value, except `open`, which preserves the last committed state and
becomes uncontrolled. An invalid `open` value does the same after reporting a
diagnostic. Other invalid client values use their server fallback.
```citry-html
...
```
`id`, `close_label`, `class_`, `style`, and `attrs` are server-only because
they define rendered identity, text, and native structure.
## Control visibility
Pass a Boolean client `open` input to control visibility. `onOpenChange`
reports user requests; update `open` to accept one or keep it unchanged to
decline it.
### Control Dialog visibility
[Open the rendered preview](/ui-library/components/dialog/_previews/controlled-dialog/)
````citry
import citry_ui
from citry import Component, citry
citry.register_library(citry_ui)
class ControlledDialog(Component):
template = """
"""
css = """
:where(.controlled-dialog) {
display: grid;
gap: 0.75rem;
justify-items: start;
max-width: 42rem;
padding: 1.25rem;
border: 1px solid light-dark(#bae6fd, #0369a1);
border-radius: 0.875rem;
background: Canvas;
color: CanvasText;
font-family: ui-sans-serif, system-ui, sans-serif;
}
:where(.controlled-dialog h2, .controlled-dialog p) {
margin: 0;
}
:where(.controlled-dialog > p:first-child) {
color: light-dark(#0369a1, #7dd3fc);
font-size: 0.75rem;
font-weight: 700;
letter-spacing: 0.08em;
text-transform: uppercase;
}
:where(.controlled-dialog__toggle) {
display: flex;
gap: 0.5rem;
align-items: center;
}
:where(.controlled-dialog__status) {
color: color-mix(in srgb, currentColor 72%, transparent);
font-size: 0.875rem;
}
"""
preview = ControlledDialog()
preview # noqa: B018
````
```citry-html
{
if (mayApply(nextOpen, detail)) open = nextOpen;
},
}"
>
...
```
The callback detail identifies the `trigger`, `close-button`, `action`,
`escape`, `outside`, or `native` reason. It also includes controlled ownership,
the browser source, and the Dialog return value. Owner commits do not notify
again.
When no client `open` input is supplied, CDialog commits requests itself and
then notifies. Passing `null` or removing the input releases control without
resetting the current state.
## Choose dismissal rules
`dismissible=True` shows the built-in close Button and permits passive
dismissal. `close_on_escape` and `close_on_outside` refine which passive paths
are allowed. All three have matching client inputs.
### Require an explicit decision
[Open the rendered preview](/ui-library/components/dialog/_previews/explicit-decision/)
````citry
import citry_ui
from citry import Component, citry
citry.register_library(citry_ui)
class ExplicitDecision(Component):
template = """
Telescope alignment
Require an explicit decision
Recalibrate telescope
Recalibrate telescope?
Observation pauses for about two minutes.
Escape, backdrop presses, and the built-in close control are unavailable.
Keep current alignment
Begin recalibration
"""
css = """
:where(.explicit-dialog) {
display: grid;
gap: 0.75rem;
justify-items: start;
max-width: 44rem;
padding: 1.25rem;
border: 1px solid light-dark(#fde68a, #a16207);
border-radius: 0.875rem;
background: Canvas;
color: CanvasText;
font-family: ui-sans-serif, system-ui, sans-serif;
}
:where(.explicit-dialog h2, .explicit-dialog p) {
margin: 0;
}
:where(.explicit-dialog > p) {
color: light-dark(#a16207, #fde68a);
font-size: 0.75rem;
font-weight: 700;
letter-spacing: 0.08em;
text-transform: uppercase;
}
"""
preview = ExplicitDecision()
preview # noqa: B018
````
With `dismissible=False`, Escape, backdrop presses, and the built-in close
control are unavailable. Actions with `close_attrs` still work, so a deliberate
workflow can always complete.
Outside dismissal requires a press that starts and ends on this Dialog's
backdrop. Dragging from content to the backdrop does not close it.
## Place initial focus
The server `initial_focus` input and matching client `initialFocus` input accept
`auto` or `title`.
### Place initial focus
[Open the rendered preview](/ui-library/components/dialog/_previews/initial-focus/)
````citry
import citry_ui
from citry import Component, citry
citry.register_library(citry_ui)
class DialogInitialFocus(Component):
template = """
Quick observation
Focus a control
Name a comet
Name a comet
Catalog name
Long report
Focus the title
Read eclipse report
Total eclipse report
Focusing the title starts reading at the top without jumping
past structured content.
Continue reading
"""
css = """
:where(.dialog-focus-grid) {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(min(100%, 18rem), 1fr));
gap: 1rem;
max-width: 60rem;
color: CanvasText;
font-family: ui-sans-serif, system-ui, sans-serif;
}
:where(.dialog-focus-grid article) {
display: grid;
gap: 0.75rem;
justify-items: start;
padding: 1.25rem;
border: 1px solid light-dark(#c4b5fd, #6d28d9);
border-radius: 0.875rem;
background: Canvas;
}
:where(.dialog-focus-grid h2, .dialog-focus-grid p) {
margin: 0;
}
:where(.dialog-focus-grid article > p) {
color: light-dark(#6d28d9, #c4b5fd);
font-size: 0.75rem;
font-weight: 700;
letter-spacing: 0.08em;
text-transform: uppercase;
}
"""
preview = DialogInitialFocus()
preview # noqa: B018
````
- `auto` keeps native `[autofocus]` and browser Dialog focus steps. Put
`autofocus` on the control that should receive focus first.
- `title` focuses the visible title. Use it for long or structured content so
reading starts at the top without jumping to a later control.
Tab and Shift+Tab stay within the nearest open Dialog. Nested Dialog controls
do not enter a parent's focus loop. Closing returns focus to the element that
was active before opening when it remains available. A workflow that needs a
different destination can focus it after the close callback.
Do not add `tabindex` to the native Dialog. CDialog owns its focus contract and
rejects that attribute.
## Scroll long content
The server `scroll` input and matching client `scroll` input accept `body` or
`dialog`.
### Scroll long Dialog content
[Open the rendered preview](/ui-library/components/dialog/_previews/long-content/)
````citry
from dataclasses import dataclass
import citry_ui
from citry import Component, citry
citry.register_library(citry_ui)
@dataclass(frozen=True, slots=True)
class ObservationEntry:
title: str
text: str
class DialogLongContent(Component):
class Kwargs:
pass
class Slots:
pass
template = """
"""
css = """
:where(.dialog-scroll-demo) {
display: grid;
gap: 0.75rem;
justify-items: start;
max-width: 46rem;
padding: 1.25rem;
border: 1px solid light-dark(#bae6fd, #0369a1);
border-radius: 0.875rem;
background: Canvas;
color: CanvasText;
font-family: ui-sans-serif, system-ui, sans-serif;
}
:where(.dialog-scroll-demo h2, .dialog-scroll-demo p) {
margin: 0;
}
:where(.dialog-scroll-demo > p) {
color: light-dark(#0369a1, #7dd3fc);
font-size: 0.75rem;
font-weight: 700;
letter-spacing: 0.08em;
text-transform: uppercase;
}
:where(.dialog-scroll-demo__entry) {
display: grid;
gap: 0.25rem;
padding-block: 0.75rem;
border-block-end: 1px solid color-mix(in srgb, currentColor 16%, transparent);
}
:where(.dialog-scroll-demo__entry span) {
color: color-mix(in srgb, currentColor 72%, transparent);
}
"""
def template_data(
self,
kwargs: Kwargs, # noqa: ARG002
slots: Slots, # noqa: ARG002
) -> dict[str, object]:
return {
"entries": tuple(
ObservationEntry(
title=f"Night {index}",
text="A clear horizon revealed Jupiter, four bright moons, and a faint silver arc.",
)
for index in range(1, 10)
)
}
preview = DialogLongContent()
preview # noqa: B018
````
- `body` keeps the header and actions visible while the body scrolls.
- `dialog` scrolls the complete surface.
Both modes stay inside the dynamic viewport. `full` fills that viewport and
removes ordinary radius, border, and shadow. Long titles and actions wrap.
## Use a native Dialog Form
A native `