Built-in UI components¶
Optional package pyjinhx.builtins registers thirty-three BaseComponent subclasses. Import:
from pyjinhx.builtins import (
PJXAlert,
PJXAvatar,
PJXAvatarStack,
PJXBadge,
PJXBreadcrumb,
PJXCard,
PJXChipInput,
PJXConfirmDialog,
PJXDivider,
PJXDropdown,
PJXDrawer,
PJXEmptyState,
PJXFormField,
PJXLazyPanel,
PJXRegionLoader,
PJXModal,
PJXNotification,
PJXPageLoader,
PJXPasswordInput,
PJXPopover,
PJXPopoverPanel,
PJXPopoverTrigger,
PJXProgress,
PJXPromptDialog,
PJXPanel,
PJXPanelTrigger,
PJXSegmentedControl,
PJXSkeleton,
PJXSpinner,
PJXTabGroup,
PJXToastHost,
PJXToggleSwitch,
PJXTooltip,
)
__all__ matches that set of thirty-three names.
Conventions: Markup classes use the pjx- prefix; overrides use --pjx- custom properties. Builtin CSS also references theme variables (--surface, --border, --text, --radius-md, --shadow-md, --transition, --brand, …)—define those in your global CSS or map them to your design system. See builtin-conventions.md for the full per-component contract (auto-id, class_name, extra_attrs, js/css, headless IIFE JS under window.pjx, cancelable pjx:*:before-* events).
Template discovery: Builtins ship inside site-packages, not under your app's Jinja loader root, so PascalCase tags do not auto-discover them — <PJXTooltip/> raises a FileNotFoundError unless the class was imported once at startup (import pyjinhx.builtins or any of the imports above), which registers it. For registered builtin classes, the renderer falls back to adjacent package templates: each component's Jinja template lives next to its Python source in pyjinhx/builtins/ui/pjx_<component>/ (e.g. pyjinhx/builtins/ui/pjx_modal/pjx_modal.html). Subclasses of builtins inherit the builtin's template and assets through the MRO, so class TaskBadge(PJXBadge) renders like a PJXBadge — see the reactivity guide for the reactive pattern.
Inherited fields: Every component inherits id, js/css (extra-asset fields — see BaseComponent), class_name, extra_attrs, render(), and __html__() — see builtin-conventions.md for the full contract. id is omitted from per-component props tables below.
Theming: Per-component --pjx-* tokens are collected in the Theming tokens appendix. Each component section points there.
Asset summary:
| Component | CSS | JS |
|---|---|---|
| PJXAlert | pjx_alert.css |
pjx_alert.js |
| PJXAvatar | pjx_avatar.css |
— |
| PJXAvatarStack | pjx-avatar-stack.css |
— |
| PJXBadge | pjx_badge.css |
— |
| PJXBreadcrumb | pjx_breadcrumb.css |
— |
| PJXCard | pjx_card.css |
— |
| PJXChipInput | pjx-chip-input.css |
pjx-chip-input.js |
| PJXConfirmDialog | pjx-confirm-dialog.css |
pjx-confirm-dialog.js |
| PJXDivider | pjx_divider.css |
— |
| PJXDrawer | pjx_drawer.css |
pjx_drawer.js |
| PJXDropdown | pjx_dropdown.css |
(via pjx_popover.js, extra-asset) |
| PJXEmptyState | pjx-empty-state.css |
— |
| PJXFormField | pjx-form-field.css |
— |
| PJXLazyPanel | — | — |
| PJXRegionLoader | pjx-region-loader.css |
pjx-region-loader.js |
| PJXModal | pjx_modal.css |
pjx_modal.js |
| PJXNotification | pjx_notification.css |
pjx_notification.js |
| PJXPageLoader | pjx-page-loader.css |
pjx-page-loader.js |
| PJXPanel | pjx_panel.css |
pjx_panel.js |
| PJXPanelTrigger | pjx-panel-trigger.css |
(pjx_panel.js from PJXPanel) |
| PJXPasswordInput | pjx-password-input.css |
pjx-password-input.js |
| PJXPopover | pjx_popover.css |
pjx_popover.js |
| PJXPopoverPanel | (from PJXPopover) | (from PJXPopover) |
| PJXPopoverTrigger | (from PJXPopover) | (from PJXPopover) |
| PJXProgress | pjx_progress.css |
— |
| PJXPromptDialog | pjx-prompt-dialog.css |
pjx-prompt-dialog.js |
| PJXSegmentedControl | pjx-segmented-control.css |
— |
| PJXSkeleton | pjx_skeleton.css |
— |
| PJXSpinner | pjx_spinner.css |
— |
| PJXTabGroup | pjx-tab-group.css |
pjx-tab-group.js |
| PJXToastHost | pjx-toast-host.css |
pjx-toast-host.js |
| PJXToggleSwitch | pjx-toggle-switch.css |
— |
| PJXTooltip | pjx-tooltip.css |
pjx-tooltip.js (IIFE, no API) |
Children-vs-content tag gotcha (children-mapping components): Several components map children to a single attribute (e.g. PJXNotification content, PJXPanelTrigger content, PJXTooltip tip). If you use Renderer.render() with PascalCase tags, do not supply both child text and the corresponding attribute on the same tag—use body text as the child or the attribute, not both.
Backdrop click (PJXModal and PJXDrawer): Both render a native <dialog>; a document click listener treats a click whose target is the <dialog> root itself (the backdrop) as a close. Any native <dialog> clicked directly is affected—use unique ids and avoid stacking multiple dialogs unless you adjust this.
pjx_panel.js loading: pjx_panel.js is included only when a PJXPanel is rendered on the page. PJXPanelTrigger does not load pjx_panel.js by asset name, so render a PJXPanel on the same page (or add the script yourself) for triggers to work.
PJXBadge¶
Small status label. Assets: pjx_badge.css only.
| Field | Type | Default | Description |
|---|---|---|---|
label |
str |
"" |
Inner text. |
color |
literal | "neutral" |
brand, error, neutral, muted → pjx-badge--{color}. |
shape |
literal | "md" |
square, sm, md, full → pjx-badge--{shape}. |
DOM contract. Root .pjx-badge; no JS API.
Classes: pjx-badge; color modifiers pjx-badge--brand, --error, --neutral, --muted; shape pjx-badge--square, --sm, --md, --full. Theming: see PJXBadge tokens.
PJXModal¶
Native <dialog>. Assets: pjx_modal.css, pjx_modal.js.
[
'<button class="pjx-demo-btn" data-pjx-open="demo-modal">Open modal</button>',
PJXModal(
id="demo-modal",
title="Confirm changes",
body="Your draft will be published immediately. This action cannot be undone.",
footer='<button class="pjx-demo-btn" data-pjx-close>Cancel</button>',
).render(),
]
| Field | Type | Default | Description |
|---|---|---|---|
title |
str \| BaseComponent |
"" |
Default header title when header is empty. |
header |
str \| BaseComponent |
"" |
If set, replaces the built-in title row. |
body |
str \| BaseComponent |
"" |
Main content; wrapper id {{ id }}-body. |
footer |
str \| BaseComponent |
"" |
If non-empty, renders <footer class="pjx-modal__footer">. |
close_label |
str |
"Close" |
aria-label for the close button. |
open_on_mount |
bool |
False |
When True, adds data-pjx-open-on-mount; JS opens the dialog as soon as it mounts (e.g. via hx-swap="beforeend"). |
remove_on_close |
bool |
False |
When True, adds data-pjx-remove-on-close; JS removes the element from the DOM on close. |
<button data-pjx-open="info-modal">Open</button>
<PJXModal id="info-modal" title="Hello" body="Content here."/>
DOM contract. Root dialog.pjx-modal (state: [open], .pjx-modal--closing).
Attributes: data-pjx-open="<id>" on any element opens it on click; data-pjx-close inside closes it;
data-pjx-open-on-mount, data-pjx-remove-on-close reflect the lifecycle props.
Events (bubble from the root): pjx:modal:before-open, pjx:modal:open,
pjx:modal:before-close, pjx:modal:close — * = cancelable, detail = {reason, trigger},
reason ∈ escape|backdrop|api|trigger. API: pjx.modal.open(id), pjx.modal.close(id).
Classes: pjx-modal; closing state pjx-modal--closing; pjx-modal__box, __header, __title, __close, __body, __footer. Theming: see PJXModal tokens.
PJXNotification¶
Fixed-position toast. Assets: pjx_notification.css, pjx_notification.js.
PJXNotification(
id="demo-notification",
content="Your changes have been saved.",
corner="top-right",
timeout=4000,
autoshow=True,
).render()
| Field | Type | Default | Description |
|---|---|---|---|
content |
str \| BaseComponent |
"" |
Message body. |
corner |
literal | "top-right" |
top-right, top-left, bottom-right, bottom-left. |
timeout |
int |
5000 |
Auto-dismiss ms; 0 disables. Rendered as data-timeout. |
autoshow |
bool |
True |
When True, adds data-pjx-autoshow; JS shows the notification as soon as it mounts. |
dismiss_label |
str |
"Dismiss" |
aria-label for the dismiss button. |
DOM contract. Root .pjx-notification (state: .pjx-notification--visible, .pjx-notification--hiding).
data-pjx-autoshow triggers auto-show on mount. data-pjx-close inside hides it.
Events: pjx:notification:before-show, pjx:notification:show, pjx:notification:before-hide, pjx:notification:hide — * = cancelable, detail = {reason, trigger}.
API: pjx.notification.show(id), pjx.notification.hide(id).
Maps children to content; see the children-vs-content note.
Classes: pjx-notification; placement pjx-notification--top-right, --top-left, --bottom-right, --bottom-left; JS state pjx-notification--visible, pjx-notification--hiding; pjx-notification__content, pjx-notification__close. Theming: see PJXNotification tokens.
PJXPopover¶
Click-toggle compound. Three separate components; compose them by placing PJXPopoverTrigger and PJXPopoverPanel as children inside PJXPopover. Assets: pjx_popover.css, pjx_popover.js (IIFE under pjx.popover).
PJXPopover(
id="demo-popover",
content=(
PJXPopoverTrigger(id="demo-popover-t", content="Show info", role="dialog").render()
+ PJXPopoverPanel(
id="demo-popover-p",
role="dialog",
content="<p>Here is some extra detail about this item.</p>",
).render()
),
).render()
PJXPopover(
id="menu",
content=(
PJXPopoverTrigger(id="menu-t", content="Open").render()
+ PJXPopoverPanel(id="menu-p", role="menu", content="…").render()
),
)
Or with PascalCase tags:
<PJXPopover id="menu">
<PJXPopoverTrigger id="menu-t">Open</PJXPopoverTrigger>
<PJXPopoverPanel id="menu-p" role="menu">…</PJXPopoverPanel>
</PJXPopover>
PJXPopover¶
Root wrapper — sets up the data-pjx-popover attribute scope.
| Field | Type | Default | Description |
|---|---|---|---|
content |
str \| BaseComponent |
"" |
Children (trigger + panel). |
align |
literal | "start" |
start or end (panel alignment) → pjx-popover--align-end. |
behavior |
bool |
True |
When True, adds data-pjx-popover (JS picks it up). |
PJXPopoverTrigger¶
| Field | Type | Default | Description |
|---|---|---|---|
content |
str \| BaseComponent |
"" |
Button/div label. |
tag |
literal | "button" |
button or div (role="button"). |
role |
literal | "" |
aria-haspopup value: menu, listbox, dialog, or "". |
behavior |
bool |
True |
When True, adds data-pjx-toggle. |
PJXPopoverPanel¶
| Field | Type | Default | Description |
|---|---|---|---|
content |
str \| BaseComponent |
"" |
Panel body. |
as_form |
bool |
False |
Render panel as <form> instead of <div>. |
role |
literal | "" |
ARIA role attribute (menu, listbox, dialog, or ""). |
behavior |
bool |
True |
When True, adds data-pjx-popover-panel (hidden by default). |
DOM contract. Root [data-pjx-popover] (the PJXPopover wrapper). Trigger: [data-pjx-toggle] on the trigger element; aria-expanded synced by JS. Panel: [data-pjx-popover-panel] element, hidden when closed. data-pjx-close inside the panel closes it. data-pjx-toggle="<panel-id>" on any element opens/closes a named panel.
Events (bubble from [data-pjx-popover]): pjx:popover:before-open, pjx:popover:open, pjx:popover:before-close, pjx:popover:close — detail = {reason, trigger}.
API: pjx.popover.open(idOrEl), pjx.popover.close(idOrEl), pjx.popover.toggle(idOrEl).
Classes: pjx-popover, pjx-popover--align-end, pjx-popover__trigger, pjx-popover__panel. Theming: see PJXPopover tokens.
PJXRegionLoader¶
In-place loading veil over a positioned ancestor. Assets: pjx-region-loader.css, pjx-region-loader.js.
[
'<div style="position:relative;min-height:6rem;padding:1rem;border:1px solid #8884;border-radius:6px">',
'<p style="margin:0">Region content — click to trigger a loading veil.</p>',
PJXRegionLoader(id="demo-region").render(),
"</div>",
"""<button class="pjx-demo-btn" style="margin-top:0.75rem"
onclick="pjx.loader.region.show('demo-region');setTimeout(()=>pjx.loader.region.hide('demo-region'),1500)">
Simulate load</button>""",
]
| Field | Type | Default | Description |
|---|---|---|---|
aria_label |
str |
"Loading" |
Accessible label (role="status"). |
Layout: Overlay is position: absolute; inset: 0. Parent must be position: relative (or any non-static value) so coverage is correct.
Supports both declarative (hx-indicator) and programmatic use (pjx.loader.region.*).
DOM contract. Root .pjx-region-loader (state: .pjx-region-loader--visible, .pjx-region-loader--hiding; also responds to .htmx-request as an htmx indicator — CSS activates the veil, no JS call required).
Events (non-cancelable): pjx:region-loader:show, pjx:region-loader:hide.
API: pjx.loader.region.show/hide/reset(id) and pjx.loader.region.wrap(id, promise). Ref-counted for concurrent sources: visible from the first show(id) to the last hide(id); show/hide events fire only on real visibility transitions (a show during an in-flight hide cancels it silently); hides finalize via a fallback timer even when animations are disabled. wrap(id, promise) pairs show/hide around any async task. Nodes replaced by a swap while sources remain active are re-lit on htmx:afterSettle.
Classes: pjx-region-loader; state pjx-region-loader--visible, pjx-region-loader--hiding; pjx-region-loader__spinner. Theming: see PJXRegionLoader tokens.
PJXTooltip¶
Compact focus/hover hint. Assets: pjx-tooltip.css, pjx-tooltip.js (IIFE — no API, behavior only).
PJXTooltip(
id="demo-tooltip",
trigger="Hover over me",
tip="This is additional context shown on hover or focus.",
placement="top",
).render()
| Field | Type | Default | Description |
|---|---|---|---|
trigger |
str \| BaseComponent |
"" |
Focusable trigger (pjx-tooltip__trigger, tabindex="0"). |
tip |
str \| BaseComponent |
"" |
role="tooltip" body. |
placement |
literal | "top" |
top, bottom, start, end → data-pjx-tooltip-placement. |
DOM contract. Root .pjx-tooltip. data-pjx-tooltip-placement drives JS positioning (top/bottom/start/end). Tip shows on mouseover anywhere inside .pjx-tooltip root, or on focusin of .pjx-tooltip__trigger; hides on mouseout/focusout; repositions on scroll. No JS API (pjx._tooltipWired guard only).
Classes: pjx-tooltip, pjx-tooltip__trigger, pjx-tooltip__tip, pjx-tooltip__tip--visible. Tip text goes through the tip attribute; tag children map to tip as well — see the children-vs-content note. Theming: see PJXTooltip tokens.
PJXAlert¶
Inline status banner. Assets: pjx_alert.css, pjx_alert.js.
PJXAlert(
id="demo-alert",
variant="warning",
title="Storage almost full",
body="You have used 90% of your storage quota. Consider removing old files.",
dismissible=True,
).render()
| Field | Type | Default | Description |
|---|---|---|---|
variant |
literal | "info" |
info, success, warning, error → pjx-alert--{variant}. |
title |
str |
"" |
Optional heading. |
body |
str \| BaseComponent |
"" |
Main copy. |
dismissible |
bool |
False |
When True, renders a dismiss button with data-pjx-close. |
dismiss_label |
str |
"Dismiss" |
aria-label for the dismiss button. |
DOM contract. Root .pjx-alert (state: .pjx-alert--dismissed — set by JS, hides via display: none).
data-pjx-close inside triggers dismissal.
Events: pjx:alert:before-dismiss* (cancelable), pjx:alert:dismiss — detail = {reason: 'trigger', trigger}.
Classes: pjx-alert; variant modifiers pjx-alert--info, --success, --warning, --error; dismissed state pjx-alert--dismissed; pjx-alert__inner, __text, __title, __body, __dismiss. Variants use color-mix with --brand, --success, --warning, or --error / --error-bg / --error-border where applicable. Theming: see PJXAlert tokens.
PJXDropdown¶
Button + anchored panel backed by the shared popover engine. Assets: pjx_dropdown.css only (ships no own JS — pjx_popover.js is included via the js extra-asset field whenever a PJXDropdown renders).
PJXDropdown(
trigger="Actions",
items=[
"<button>Edit</button>",
"<button>Duplicate</button>",
"<button>Delete</button>",
],
menu_label="Actions menu",
).render()
| Field | Type | Default | Description |
|---|---|---|---|
trigger |
str \| BaseComponent |
"" |
Button label. |
items |
list[str \| BaseComponent] |
[] |
Menu items rendered inside the panel. |
align |
literal | "start" |
start or end → pjx-dropdown--align-end. |
menu_label |
str |
"Submenu" |
aria-label on the menu panel. |
behavior |
bool |
True |
When False, removes all data-pjx-* wiring. |
Trigger id is {{ id }}-trigger, menu is {{ id }}-menu.
DOM contract. Root .pjx-dropdown with data-pjx-popover. Trigger: button.pjx-dropdown__trigger with data-pjx-toggle="{{ id }}-menu", aria-expanded synced by pjx_popover.js. Panel: div.pjx-dropdown__menu[data-pjx-popover-panel][role="menu"], hidden when closed. All popover events and API apply: pjx.popover.open/close/toggle(panelId). Document click outside closes the menu; Escape closes all open popovers.
Classes: pjx-dropdown, pjx-dropdown--align-end, pjx-dropdown__trigger, pjx-dropdown__menu. Theming: see PJXDropdown tokens.
PJXDrawer¶
<dialog> sheet from an edge. Assets: pjx_drawer.css, pjx_drawer.js.
[
'<button class="pjx-demo-btn" data-pjx-open="demo-drawer">Open drawer</button>',
PJXDrawer(
id="demo-drawer",
side="right",
title="Filter results",
body="<p>Adjust filters to narrow down your results.</p>",
footer='<button class="pjx-demo-btn" data-pjx-close>Done</button>',
).render(),
]
| Field | Type | Default | Description |
|---|---|---|---|
side |
literal | "right" |
left, right, or bottom → pjx-drawer--{side}. |
title |
str \| BaseComponent |
"" |
Header title. |
body |
str \| BaseComponent |
"" |
Scrollable body. |
footer |
str \| BaseComponent |
"" |
Optional footer strip. |
close_label |
str |
"Close" |
aria-label for the close button. |
open_on_mount |
bool |
False |
Adds data-pjx-open-on-mount; JS opens on arrival. |
remove_on_close |
bool |
False |
Adds data-pjx-remove-on-close; JS removes element on close. |
DOM contract. Root dialog.pjx-drawer (state: [open], .pjx-drawer--closing).
data-pjx-open="<id>" on any element opens it; data-pjx-close inside closes it;
data-pjx-open-on-mount, data-pjx-remove-on-close reflect lifecycle props.
Events: pjx:drawer:before-open, pjx:drawer:open, pjx:drawer:before-close, pjx:drawer:close — * = cancelable, detail = {reason, trigger}, reason ∈ escape|backdrop|api|trigger.
API: pjx.drawer.open(id), pjx.drawer.close(id).
Classes: pjx-drawer; side modifiers pjx-drawer--left, --right, --bottom; closing state pjx-drawer--closing; pjx-drawer__box, __header, __title, __close, __body, __footer. Backdrop click closes the dialog (see intro note). Theming: see PJXDrawer tokens.
PJXProgress¶
Determinate or indeterminate meter. Assets: pjx_progress.css only.
| Field | Type | Default | Description |
|---|---|---|---|
value |
float \| None |
None |
Omit or None for indeterminate <progress>. |
max |
float |
100 |
Passed to <progress max="…">. |
label |
str |
"" |
Optional pjx-progress__label; wires aria-labelledby when set. |
loading_label |
str |
"Loading" |
aria-label fallback on <progress> when label is empty. |
DOM contract. Root .pjx-progress; no JS API.
Classes: pjx-progress, pjx-progress__label, pjx-progress__bar. Theming: see PJXProgress tokens.
PJXSkeleton¶
Placeholder shimmer blocks. Assets: pjx_skeleton.css only.
| Field | Type | Default | Description |
|---|---|---|---|
variant |
literal | "text" |
text (stacked lines), circle, or rect. |
lines |
int |
3 |
For text, count of pjx-skeleton__line rows. |
DOM contract. Root .pjx-skeleton; no JS API.
Classes: pjx-skeleton; variant modifiers pjx-skeleton--text, --circle, --rect; pjx-skeleton__line, pjx-skeleton__circle, pjx-skeleton__rect. Theming: see PJXSkeleton tokens.
PJXEmptyState¶
Centered empty view. Assets: pjx-empty-state.css only (template file pjx-empty-state.html next to pjx_empty_state.py).
PJXEmptyState(
title="No results",
description="Try a different search term.",
actions=['<button class="pjx-demo-btn">Clear filters</button>'],
).render()
| Field | Type | Default | Description |
|---|---|---|---|
image |
str \| BaseComponent |
"" |
Optional slot above the heading (e.g. illustration markup). |
title |
str \| BaseComponent |
"" |
Heading. |
description |
str \| BaseComponent |
"" |
Supporting text. |
action |
str \| BaseComponent |
"" |
Optional slot (e.g. button markup). |
actions |
list[str \| BaseComponent] |
[] |
Optional flex row of slots; renders after action when both are set. |
DOM contract. Root .pjx-empty-state; no JS API.
Classes: pjx-empty-state, pjx-empty-state__image, pjx-empty-state__title, pjx-empty-state__desc, pjx-empty-state__action, pjx-empty-state__actions. Theming: see PJXEmptyState tokens.
PJXLazyPanel¶
HTMX lazy-load wrapper: a single div that fetches url on a computed trigger and swaps itself with the response. Assets: none.
| Field | Type | Default | Description |
|---|---|---|---|
url |
str |
required | Endpoint for the deferred content (hx-get). |
when |
literal | "viewport" |
viewport (scroll-revealed), reveal (pjx:reveal from nearest [data-pjx-region]), load (immediately). Overridden by trigger. |
trigger |
str |
"" |
Explicit hx-trigger value; overrides when entirely when set. |
swap |
str |
"outerHTML" |
hx-swap strategy. |
content |
str \| BaseComponent |
"" |
Placeholder shown until the fetch lands (e.g. a PJXSkeleton). |
when preset mapping:
when |
hx-trigger value |
|---|---|
viewport (default) |
revealed |
reveal |
pjx:reveal from:closest [data-pjx-region] once |
load |
load |
DOM contract. Root .pjx-lazy-panel; no JS (pure HTMX). data-pjx-region on a PJXPanel or PJXPanelTrigger host fires pjx:reveal/pjx:before-reveal events that when="reveal" listens for.
Classes: pjx-lazy-panel (unstyled hook). No theming tokens.
PJXDivider¶
Separator line. Assets: pjx_divider.css only.
| Field | Type | Default | Description |
|---|---|---|---|
orientation |
literal | "horizontal" |
horizontal (default hr) or vertical (bar). |
label |
str |
"" |
If set with horizontal orientation, flex row with label between lines. |
DOM contract. Root .pjx-divider; no JS API.
Classes: pjx-divider--horizontal, pjx-divider--vertical, pjx-divider--labeled, pjx-divider__line, pjx-divider__label. Theming: see PJXDivider tokens.
PJXSpinner¶
Inline loading indicator. Assets: pjx_spinner.css only.
| Field | Type | Default | Description |
|---|---|---|---|
size |
literal | "md" |
sm, md, or lg. |
label |
str |
"Loading" |
Visually hidden; exposed to assistive tech. |
DOM contract. Root .pjx-spinner; no JS API.
Classes: pjx-spinner, pjx-spinner--sm|md|lg, pjx-spinner__ring, pjx-spinner__label (screen-reader-only). Theming: see PJXSpinner tokens.
PJXAvatar¶
Image or initials in a circle. Assets: pjx_avatar.css only.
| Field | Type | Default | Description |
|---|---|---|---|
src |
str |
"" |
Image URL; when empty, initials fallback is shown. |
alt |
str |
"" |
img alt text; also used as title on initials. |
initials |
str |
"" |
Up to two characters (trimmed/capped in validation). |
size |
literal | "md" |
sm, md, or lg. |
color |
str |
"" |
Extra class or inline color hint (appended to root classes). |
DOM contract. Root .pjx-avatar; no JS API.
Classes: pjx-avatar, pjx-avatar--sm|md|lg, pjx-avatar__img, pjx-avatar__initials. Theming: see PJXAvatar tokens.
PJXCard¶
Grouped content with optional header and footer. Assets: pjx_card.css only.
PJXCard(title="Quarterly report", body="Revenue grew 12% over Q1.", footer="Updated today").render()
| Field | Type | Default | Description |
|---|---|---|---|
title |
str \| BaseComponent |
"" |
Default header title (ignored if header is set). |
header |
str \| BaseComponent |
"" |
Custom header slot; replaces title block when set. |
body |
str \| BaseComponent |
"" |
Main content. |
footer |
str \| BaseComponent |
"" |
Optional footer. |
DOM contract. Root .pjx-card; no JS API.
Classes: pjx-card, pjx-card__header, pjx-card__title, pjx-card__body, pjx-card__footer. Theming: see PJXCard tokens.
PJXBreadcrumb¶
Ordered trail of links. Assets: pjx_breadcrumb.css only.
| Field | Type | Default | Description |
|---|---|---|---|
items |
list[tuple[str, str \| None]] |
[] |
(label, href) left to right; href None marks the current page. |
aria_label |
str |
"PJXBreadcrumb" |
aria-label on the <nav> wrapper. |
items may also be passed as a JSON array string (e.g. from PascalCase tags): [["Home","/"],["Here",null]].
DOM contract. Root .pjx-breadcrumb; no JS API.
Classes: pjx-breadcrumb, pjx-breadcrumb__list, pjx-breadcrumb__item, pjx-breadcrumb__link, pjx-breadcrumb__current. Separators via ::after on items except the last. Theming: see PJXBreadcrumb tokens.
PJXTabGroup¶
Tab buttons and panels. Assets: pjx-tab-group.css, pjx-tab-group.js.
PJXTabGroup(
tabs={
"Overview": "<p>Project summary and key metrics.</p>",
"Activity": "<p>Recent commits and deploys.</p>",
"Settings": "<p>Repository configuration.</p>",
},
tabs_label="Project tabs",
).render()
| Field | Type | Default | Description |
|---|---|---|---|
tabs |
dict[str, str \| BaseComponent] |
{} |
Insertion order is tab order; keys are labels, values are panel bodies. |
tabs_label |
str |
"Tabs" |
aria-label for the tab list. |
tabs may also be a JSON object string from markup tags (values are HTML strings).
DOM contract. Root .pjx-tab-group (no data-pjx-region on the root). Tab elements: .pjx-tab-group__tab (no data-pjx-panel-key — tabs match panels by insertion-order index); panel elements: .pjx-tab-group__panel[data-pjx-region] (each panel carries data-pjx-region). pjx:before-reveal (cancelable) fires before switching; pjx:reveal fires after; data-pjx-revealed is set on the visible panel. pjx-tab-group.js delegates click document-wide on .pjx-tab-group__tab (not scoped to .pjx-tab-group__list), updates aria-selected, tabindex, and hidden. On DOMContentLoaded, htmx:afterSwap, and htmx:afterSettle, pxTabGroupInit announces the initially visible panel once (pjx:reveal, reason "api") so PJXLazyPanel(when="reveal") works in default tabs.
Classes: pjx-tab-group, pjx-tab-group__list, pjx-tab-group__tab, pjx-tab-group__panel. Theming: see PJXTabGroup tokens.
PJXPanel¶
Host for distributed tab-like switching: all bodies render here; controls are separate PJXPanelTrigger components. Unstyled aside from hidden panels. Assets: pjx_panel.css, pjx_panel.js.
[
'<div style="display:flex;gap:0.5rem;margin-bottom:1rem">',
PJXPanelTrigger(
panel_id="demo-panel",
panel="chat",
content='<button class="pjx-demo-btn">Chat</button>',
).render(),
PJXPanelTrigger(
panel_id="demo-panel",
panel="files",
content='<button class="pjx-demo-btn">Files</button>',
).render(),
PJXPanelTrigger(
panel_id="demo-panel",
panel="settings",
content='<button class="pjx-demo-btn">Settings</button>',
).render(),
"</div>",
PJXPanel(
id="demo-panel",
panels={
"chat": "<p>Active conversations with your team.</p>",
"files": "<p>Uploaded assets and project documents.</p>",
"settings": "<p>Notification preferences and integrations.</p>",
},
).render(),
]
| Field | Type | Default | Description |
|---|---|---|---|
panels |
dict[str, str \| BaseComponent] |
{} |
Insertion order sets the initially visible slot (first is shown). Keys must match [a-zA-Z0-9_-]+. |
panels may be a JSON object string from PascalCase tags. Keys are used in HTML id attributes and data-pjx-panel-key. Slot element ids are {{ id }}-panel-{{ key }}. The id must match PJXPanelTrigger.panel_id.
DOM contract. Root .pjx-panel with id. Panels: .pjx-panel__panel[data-pjx-panel-key][data-pjx-region]. Events (bubble from the active panel): pjx:before-reveal* (cancelable, detail = {reason, trigger}), pjx:reveal; data-pjx-revealed on the visible panel. pjx_panel.js click delegation on .pjx-panel-trigger; pxPanelInit runs on DOMContentLoaded, htmx:afterSwap, htmx:afterSettle.
Classes: pjx-panel, pjx-panel__panel. No theme tokens; minimal rules for [hidden] panels.
PJXPanelTrigger¶
Invisible wrapper that wires clicks to a PJXPanel slot. Assets: pjx-panel-trigger.css. See the pjx_panel.js loading note—render a PJXPanel on the same page so the script is included.
| Field | Type | Default | Description |
|---|---|---|---|
panel_id |
str |
(required) | Must equal the target PJXPanel.id. |
panel |
str |
(required) | Key matching a key in PJXPanel.panels; [a-zA-Z0-9_-]+. |
content |
str \| BaseComponent |
"" |
Inner HTML / nested components (your visible control). |
Maps children to content; see the children-vs-content note.
DOM contract. Root .pjx-panel-trigger[data-pjx-panel-id][data-pjx-panel-key]; no own JS (wired by pjx_panel.js). display: contents so no layout box. On every panel switch, pjx_panel.js syncs aria-selected and tabindex on every .pjx-panel-trigger[data-pjx-panel-id] matching the host PJXPanel.
Classes: pjx-panel-trigger. No theming tokens.
PJXConfirmDialog¶
Accessible <dialog> singleton that replaces window.confirm. Mount once in the layout; pjx.confirm() is available everywhere. Assets: pjx-confirm-dialog.css, pjx-confirm-dialog.js.
[
"""<button class="pjx-demo-btn"
onclick="pjx.confirm('Delete this file?', { okLabel: 'Delete', danger: true })
.then(ok => { if (ok) alert('Deleted.') })">Delete file</button>""",
PJXConfirmDialog(id="demo-confirm").render(),
]
| Field | Type | Default | Description |
|---|---|---|---|
confirm_label |
str |
"Confirm" |
Default OK button text. |
cancel_label |
str |
"Cancel" |
Default cancel button text. |
Intercepts every hx-confirm="…" automatically (via htmx:confirm event):
For non-htmx forms use data-confirm="…" on the <form>.
Override labels per-call:
const ok = await pjx.confirm("Are you sure?", {
okLabel: "Yes, delete",
cancelLabel: "No",
danger: true,
});
if (ok) { /* proceed */ }
DOM contract. Root dialog.pjx-confirm-dialog[data-pjx-dialog="confirm"] — singleton, matched by document.querySelector. data-pjx-confirm-danger on the htmx element → OK button gets .pjx-confirm-dialog__ok--danger. data-pjx-confirm-ok / data-pjx-confirm-cancel per-trigger label overrides.
API: pjx.confirm(message, {okLabel?, cancelLabel?, danger?}) → Promise<boolean>.
Falls back to window.confirm if no PJXConfirmDialog is mounted.
Classes: pjx-confirm-dialog, pjx-confirm-dialog__card, pjx-confirm-dialog__message, pjx-confirm-dialog__actions, pjx-confirm-dialog__ok, pjx-confirm-dialog__ok--danger, pjx-confirm-dialog__cancel. Theming: see PJXConfirmDialog tokens.
PJXPromptDialog¶
Accessible <dialog> singleton that replaces window.prompt. Mount once in the layout; pjx.prompt() is available everywhere. Assets: pjx-prompt-dialog.css, pjx-prompt-dialog.js.
[
"""<button class="pjx-demo-btn"
onclick="pjx.prompt('Rename file', { initial: 'report.pdf', placeholder: 'New name' })
.then(name => { if (name !== null) alert('Renamed to: ' + name) })">Rename file</button>""",
PJXPromptDialog(id="demo-prompt").render(),
]
| Field | Type | Default | Description |
|---|---|---|---|
input_label |
str |
"" |
Default label text above the input. |
submit_label |
str |
"OK" |
Submit button text. |
cancel_label |
str |
"Cancel" |
Cancel button text. |
const name = await pjx.prompt("Enter your name", {
initial: "Alice",
placeholder: "Full name",
okLabel: "Save",
});
if (name !== null) { /* user submitted */ }
DOM contract. Root dialog.pjx-prompt-dialog[data-pjx-dialog="prompt"] — singleton, matched by document.querySelector. Input pre-focused and selected on open.
API: pjx.prompt(title, {initial?, placeholder?, okLabel?, cancelLabel?}) → Promise<string | null>.
Returns null on cancel/Escape/backdrop close. Falls back to window.prompt if no PJXPromptDialog is mounted.
Classes: pjx-prompt-dialog, pjx-prompt-dialog__card, pjx-prompt-dialog__label, pjx-prompt-dialog__input, pjx-prompt-dialog__actions, pjx-prompt-dialog__ok, pjx-prompt-dialog__cancel. Theming: see PJXPromptDialog tokens.
PJXToastHost¶
HX-Trigger-driven toast container singleton. Mount once in the layout. Assets: pjx-toast-host.css, pjx-toast-host.js.
[
'<button class="pjx-demo-btn" onclick="pjx.toast(\'Saved.\')">Show toast</button>',
PJXToastHost(position="bottom-right").render(),
]
| Field | Type | Default | Description |
|---|---|---|---|
position |
literal | "bottom-right" |
top-right, top-left, bottom-right, bottom-left. |
timeout |
int |
4000 |
Default auto-dismiss ms; 0 disables. |
dismiss_label |
str |
"Dismiss" |
aria-label for dismiss buttons on individual toasts. |
event_name |
str |
"pjx:toast" |
Custom event name to listen for (wired on mount). |
Fire from a FastAPI route via HX-Trigger:
import json
response.headers["HX-Trigger"] = json.dumps({"pjx:toast": {"message": "Saved.", "level": "success"}})
Or from JS:
Toast level maps to .pjx-toast--<level>; supported values: info, success, warning, error.
DOM contract. Root div.pjx-toast-host[data-pjx-toast-host]. data-event-name sets the custom event; data-timeout sets the default dismiss timeout; data-dismiss-label sets dismiss button label.
Events (bubble from the host): pjx:toasthost:show (detail: {level}), pjx:toasthost:hide.
API: pjx.toast(message, {level?, timeout?}).
Individual toasts: div.pjx-toast.pjx-toast--<level> > .pjx-toast__message + button.pjx-toast__dismiss.
Classes: pjx-toast-host, pjx-toast-host--top-right, --top-left, --bottom-right, --bottom-left; pjx-toast, pjx-toast--info, --success, --warning, --error; pjx-toast--hiding; pjx-toast__message, pjx-toast__dismiss. Theming: see PJXToastHost tokens.
PJXAvatarStack¶
Overlapping row of avatars with optional overflow count. Assets: pjx-avatar-stack.css only.
PJXAvatarStack(
avatars=[
PJXAvatar(initials="AB", size="sm", alt="Alice Brown"),
PJXAvatar(initials="CD", size="sm", alt="Carol Davis"),
PJXAvatar(initials="EF", size="sm", alt="Eve Foster"),
],
extra_count=4,
).render()
| Field | Type | Default | Description |
|---|---|---|---|
avatars |
list[str \| BaseComponent] |
[] |
PJXAvatar items (typically PJXAvatar components or HTML strings). |
extra_count |
int |
0 |
When > 0, renders a +N overflow chip. |
empty_label |
str |
"" |
When no avatars and empty_label is set, renders a fallback label. |
PJXAvatarStack(
id="team",
avatars=[
PJXAvatar(id="a1", initials="AB", size="sm"),
PJXAvatar(id="a2", initials="CD", size="sm"),
],
extra_count=3,
)
DOM contract. Root .pjx-avatar-stack; no JS API.
Classes: pjx-avatar-stack, pjx-avatar-stack__more, pjx-avatar-stack__empty. Theming: see PJXAvatarStack tokens.
PJXPageLoader¶
Full-page navigation loader. Mount once at the top of the layout body. Assets: pjx-page-loader.css, pjx-page-loader.js.
[
PJXPageLoader(id="demo-page-loader").render(),
"""<button class="pjx-demo-btn" style="margin-top:0.75rem"
onclick="pjx.loader.page.show();setTimeout(()=>pjx.loader.page.hide(),1500)">
Simulate load</button>""",
]
| Field | Type | Default | Description |
|---|---|---|---|
nav_targets |
str |
"app-content" |
Comma-separated element ids whose htmx GET requests activate the loader. |
active_on_load |
bool |
True |
When True, renders with .pjx-page-loader--active (cold-load shimmer, removed on DOMContentLoaded). |
loading_label |
str |
"Loading" |
aria-label for the role="status" element. |
Add data-pjx-loader to any element to make its htmx requests activate the loader regardless of nav_targets:
DOM contract. Root div.pjx-page-loader[data-pjx-page-loader] (state: .pjx-page-loader--active).
data-nav-targets — comma-separated ids; htmx GET requests targeting any of these activate the loader.
data-pjx-loader on any element marks its htmx requests as loader-tracked regardless of target.
Tracking is detected via htmx:beforeRequest; the loader releases via the request's loadend (terminal on load, error, abort, and timeout); history navigations reset via htmx:historyRestore.
Events (non-cancelable, bubble from the root): pjx:page-loader:show, pjx:page-loader:hide.
API: pjx.loader.page.show(), pjx.loader.page.hide(), pjx.loader.page.reset(), pjx.loader.page.wrap(promise).
Classes: pjx-page-loader, pjx-page-loader--active, pjx-page-loader__spinner. Theming: see PJXPageLoader tokens.
Form controls¶
PJXChipInput¶
Tag-style multi-value input. Each chip carries its own <input type="hidden"> so values post with any enclosing form and removal is pure DOM removal. Assets: pjx-chip-input.css, pjx-chip-input.js.
| Field | Type | Default | Description |
|---|---|---|---|
name |
str |
— | name attribute on the hidden inputs and the data-name data attribute. |
values |
list[str] |
[] |
Initial chip values. |
placeholder |
str |
"Add…" |
Placeholder text in the text field. |
remove_label |
str |
"Remove" |
aria-label on every remove button; also stored as data-remove-label so JS can copy it into dynamically built chips. |
disabled |
bool |
False |
When True, no text field or remove buttons are rendered; data-disabled is set on the root. |
DOM contract. Root div.pjx-chip-input[data-pjx-chip-input][data-name][data-remove-label]; state: [data-disabled].
Each chip: span.pjx-chip-input__chip[data-pjx-chip] containing a .pjx-chip-input__label, input[type=hidden], and (when enabled) button.pjx-chip-input__remove[data-pjx-chip-remove].
Text field: input.pjx-chip-input__field.
Events (bubble from root): pjx:chip-input:before-add (detail {value}), pjx:chip-input:add, pjx:chip-input:before-remove (detail {value}), pjx:chip-input:remove — * = cancelable. Commit triggers: Enter, ,, focusout, submit (form submit commits pending text); Backspace on empty field removes the last chip.
Classes: pjx-chip-input, pjx-chip-input__chip, pjx-chip-input__label, pjx-chip-input__remove, pjx-chip-input__field. Theming: see PJXChipInput tokens.
Notes:
valuesrender verbatim (the renderer unescapes markup by design) — sanitize user-supplied tag text server-side before re-rendering it.- Duplicate entries are silently dropped (the field clears).
- Chips added client-side exist only in the DOM until the form posts; an htmx swap of the surrounding region re-renders from server state.
PJXFormField¶
Labelled control wrapper with help text and error state. Assets: pjx-form-field.css only.
PJXFormField(
label="Email address",
for_id="demo-email",
content='<input id="demo-email" type="email" name="email" placeholder="you@example.com">',
help="We'll never share your email with anyone.",
required=True,
).render()
| Field | Type | Default | Description |
|---|---|---|---|
label |
str |
"" |
Label text; renders <label> only when non-empty. |
for_id |
str |
"" |
When set, adds for="{{ for_id }}" to the label. |
content |
str \| BaseComponent |
"" |
Control HTML (an <input>, <select>, etc.). |
help |
str |
"" |
Help text shown below the control — suppressed when error is set. |
error |
str |
"" |
Error message; adds pjx-form-field--error to the root and a role="alert" paragraph with id {{ id }}-error. |
required |
bool |
False |
Renders a <span class="pjx-form-field__required" aria-hidden="true">*</span> inside the label. |
PJXFormField(
id="email-field",
label="Email",
for_id="email-input",
content='<input id="email-input" type="email" name="email">',
error="Please enter a valid email address.",
required=True,
)
DOM contract. Root div.pjx-form-field; error state div.pjx-form-field--error. Help paragraph id: {{ id }}-help. Error paragraph id: {{ id }}-error, role="alert". No JS.
Classes: pjx-form-field, pjx-form-field--error, pjx-form-field__label, pjx-form-field__required, pjx-form-field__control, pjx-form-field__help, pjx-form-field__error. Theming: see PJXFormField tokens.
PJXToggleSwitch¶
Accessible on/off toggle backed by a visually-hidden checkbox. Assets: pjx-toggle-switch.css only — no JS.
| Field | Type | Default | Description |
|---|---|---|---|
name |
str |
"" |
name attribute on the hidden checkbox. |
value |
str |
"on" |
value attribute on the checkbox. |
checked |
bool |
False |
When True, adds checked to the checkbox. |
label |
str |
"" |
Visible label text rendered after the track; omitted when empty. |
disabled |
bool |
False |
When True, adds disabled to the checkbox. |
DOM contract. Root label.pjx-toggle-switch wrapping a visually-hidden input[type=checkbox].pjx-toggle-switch__input (uses clip-path: inset(50%), not display:none — keeps focus and click). CSS keys on :checked + .pjx-toggle-switch__track for the active state and :focus-visible + .pjx-toggle-switch__track for the ring. No JS API.
Classes: pjx-toggle-switch, pjx-toggle-switch__input, pjx-toggle-switch__track, pjx-toggle-switch__thumb, pjx-toggle-switch__label. Theming: see PJXToggleSwitch tokens.
PJXSegmentedControl¶
Pill-style radio group for mutually exclusive options. Assets: pjx-segmented-control.css only — no JS.
PJXSegmentedControl(
name="view",
options=[("list", "List"), ("grid", "Grid"), ("table", "Table")],
selected="grid",
).render()
| Field | Type | Default | Description |
|---|---|---|---|
name |
str |
— | name on all radio inputs. |
options |
list[tuple[str, str]] |
[] |
(value, label) pairs. Also accepts a JSON string when passed as a tag attribute. |
selected |
str |
"" |
Value of the pre-checked option. |
disabled |
bool |
False |
When True, adds disabled to all radio inputs. |
PJXSegmentedControl(
id="view-switcher",
name="view",
options=[("list", "List"), ("grid", "Grid"), ("table", "Table")],
selected="list",
)
DOM contract. Root div.pjx-segmented-control[role="radiogroup"]. Each option: label.pjx-segmented-control__segment > input[type=radio].pjx-segmented-control__input (visually hidden, clip-path: inset(50%)) + span.pjx-segmented-control__text. CSS keys on :checked + .pjx-segmented-control__text for the active segment. No JS API.
Classes: pjx-segmented-control, pjx-segmented-control__segment, pjx-segmented-control__input, pjx-segmented-control__text. Theming: see PJXSegmentedControl tokens.
PJXPasswordInput¶
Password field with a show/hide toggle button. Assets: pjx-password-input.css, pjx-password-input.js.
PJXPasswordInput(
name="password",
placeholder="Enter your password",
autocomplete="current-password",
required=True,
).render()
| Field | Type | Default | Description |
|---|---|---|---|
name |
str |
"password" |
name on the <input>. |
placeholder |
str |
"" |
placeholder on the <input>; omitted when empty. |
autocomplete |
str |
"current-password" |
autocomplete attribute. |
required |
bool |
False |
When True, adds required to the <input>. |
show_label |
str |
"Show password" |
Static aria-label on the toggle button; visibility state is conveyed by aria-pressed (ARIA toggle-button pattern). |
DOM contract. Root div.pjx-password-input[data-pjx-password]. Field: input.pjx-password-input__field with id {{ id }}-field. Toggle button: button.pjx-password-input__toggle[data-pjx-password-toggle]; aria-pressed reflects state ("false" when hidden, "true" when shown); .pjx-password-input__toggle--on class added when visible. No pjx:* events — state is readable from aria-pressed on the button. No JS API under window.pjx.
Classes: pjx-password-input, pjx-password-input__field, pjx-password-input__toggle, pjx-password-input__toggle--on, pjx-password-input__eye. Theming: see PJXPasswordInput tokens.
Example¶
from pyjinhx.builtins import (
PJXAvatarStack,
PJXBadge,
PJXBreadcrumb,
PJXCard,
PJXConfirmDialog,
PJXDrawer,
PJXModal,
PJXNotification,
PJXPageLoader,
PJXPanel,
PJXPanelTrigger,
PJXTabGroup,
PJXToastHost,
PJXTooltip,
)
badge = PJXBadge(id="status-badge", label="Beta", color="brand")
modal = PJXModal(id="info-modal", title="Hello", body="Content here.")
toast = PJXNotification(id="welcome-toast", content="Saved.", corner="bottom-right", timeout=3000)
drawer = PJXDrawer(id="filters", side="right", title="Filters", body="…")
tip = PJXTooltip(id="help-tip", trigger="?", tip="More detail", placement="top")
card = PJXCard(id="summary", title="Summary", body="Details go here.")
crumb = PJXBreadcrumb(id="crumb", items=[("App", "/"), ("Page", None)])
tabs = PJXTabGroup(
id="main-tabs",
tabs={"A": "<p>First</p>", "B": "<p>Second</p>"},
)
main_panel = PJXPanel(
id="app-panels",
panels={"chat": "<p>Chat UI</p>", "other": "<p>Other</p>"},
)
open_chat = PJXPanelTrigger(
id="open-chat", panel_id="app-panels", panel="chat", content="Chat"
)
confirm = PJXConfirmDialog(id="app-confirm")
toasts = PJXToastHost(id="app-toasts", position="bottom-right")
page_loader = PJXPageLoader(id="page-loader")
avatar_stack = PJXAvatarStack(id="team", avatars=[], extra_count=5)
Theming tokens¶
Per-component --pjx-* custom properties and their default mappings. Override any token in your own CSS.
PJXBadge tokens¶
| Token | Default (maps to) |
|---|---|
--pjx-badge-font-size |
var(--font-size-xs) |
--pjx-badge-radius-sm |
var(--radius-sm) |
--pjx-badge-radius-md |
var(--radius-md) |
--pjx-badge-radius-full |
var(--radius-full) |
--pjx-badge-brand-bg |
var(--brand-subtle) |
--pjx-badge-brand-fg |
var(--brand-muted) |
--pjx-badge-brand-accent |
var(--brand) |
--pjx-badge-error-bg |
var(--error-bg) |
--pjx-badge-error-fg |
var(--error) |
--pjx-badge-error-border |
var(--error-border) |
--pjx-badge-neutral-bg |
var(--surface-alt) |
--pjx-badge-neutral-fg |
var(--text) |
--pjx-badge-neutral-border |
var(--border) |
--pjx-badge-muted-bg |
var(--surface) |
--pjx-badge-muted-fg |
var(--text-muted) |
--pjx-badge-muted-border |
var(--border) |
PJXModal tokens¶
| Token | Default |
|---|---|
--pjx-modal-width |
52rem |
--pjx-modal-min-height |
28rem |
--pjx-modal-bg |
var(--surface) |
--pjx-modal-border |
var(--border) |
--pjx-modal-radius |
var(--radius-lg) |
--pjx-modal-shadow |
var(--shadow-md) |
--pjx-modal-header-bg |
var(--surface-alt) |
--pjx-modal-header-sep |
var(--border) |
--pjx-modal-footer-bg |
var(--surface-alt) |
--pjx-modal-footer-sep |
var(--border) |
--pjx-modal-title-color |
var(--text) |
--pjx-modal-close-color |
var(--text-muted) |
--pjx-modal-backdrop |
rgb(0 0 0 / 0.6) |
--pjx-modal-padding |
1.5rem |
Close control hover uses var(--surface), var(--text), var(--radius-sm), var(--transition) from your theme.
PJXNotification tokens¶
| Token | Default |
|---|---|
--pjx-notification-width |
22rem |
--pjx-notification-gap |
1.25rem (viewport inset; used in slide animations) |
--pjx-notification-bg |
var(--surface-alt) |
--pjx-notification-border |
var(--border) |
--pjx-notification-radius |
var(--radius-md) |
--pjx-notification-shadow |
var(--shadow-md) |
--pjx-notification-padding |
1rem 1rem 1rem 1.25rem |
--pjx-notification-close-color |
var(--text-muted) |
--pjx-notification-z |
500 |
Content uses var(--font-size-sm), var(--text); close hover uses var(--surface), var(--text), var(--radius-sm), var(--transition).
PJXPopover tokens¶
| Token | Default |
|---|---|
--pjx-popover-max-width |
28ch |
--pjx-popover-bg |
var(--surface-alt) |
--pjx-popover-border |
var(--border) |
--pjx-popover-radius |
var(--radius-md) |
--pjx-popover-shadow |
var(--shadow-md) |
--pjx-popover-padding |
var(--space-3, 0.75rem) var(--space-4, 1rem) |
--pjx-popover-z |
300 |
PJXRegionLoader tokens¶
| Token | Default |
|---|---|
--pjx-region-loader-bg |
rgb(0 0 0 / 0.55) |
--pjx-region-loader-backdrop |
blur(2px) |
--pjx-region-loader-z |
100 |
--pjx-region-loader-spinner-size |
2.5rem |
--pjx-region-loader-spinner-width |
3px |
--pjx-region-loader-spinner-color |
var(--brand) |
--pjx-region-loader-spinner-track |
rgb(255 255 255 / 0.1) |
PJXTooltip tokens¶
| Token | Default |
|---|---|
--pjx-tooltip-gap |
6px |
--pjx-tooltip-bg |
var(--surface-alt) |
--pjx-tooltip-border |
var(--border) |
--pjx-tooltip-radius |
var(--radius-sm) |
--pjx-tooltip-shadow |
var(--shadow-md) |
--pjx-tooltip-padding |
0.35rem 0.5rem |
--pjx-tooltip-max-width |
20ch |
--pjx-tooltip-fg |
var(--text) |
--pjx-tooltip-font-size |
var(--font-size-xs) |
--pjx-tooltip-z |
400 |
PJXAlert tokens¶
| Token | Default |
|---|---|
--pjx-alert-radius |
var(--radius-md) |
--pjx-alert-padding |
0.875rem 1rem |
--pjx-alert-border |
var(--border) |
--pjx-alert-shadow |
var(--shadow-md) |
--pjx-alert-title-size |
var(--font-size-sm) |
--pjx-alert-body-size |
var(--font-size-sm) |
--pjx-alert-dismiss-color |
var(--text-muted) |
PJXDropdown tokens¶
| Token | Default |
|---|---|
--pjx-dropdown-menu-bg |
var(--surface-alt) |
--pjx-dropdown-menu-border |
var(--border) |
--pjx-dropdown-menu-radius |
var(--radius-md) |
--pjx-dropdown-menu-shadow |
var(--shadow-md) |
--pjx-dropdown-menu-padding |
0.35rem 0 |
--pjx-dropdown-menu-min-w |
10rem |
--pjx-dropdown-menu-max-h |
min(70dvh, 24rem) |
--pjx-dropdown-z |
350 |
PJXDrawer tokens¶
| Token | Default |
|---|---|
--pjx-drawer-width |
min(24rem, 100vw) |
--pjx-drawer-height-bottom |
min(50dvh, 28rem) |
--pjx-drawer-bg |
var(--surface) |
--pjx-drawer-border |
var(--border) |
--pjx-drawer-shadow |
var(--shadow-md) |
--pjx-drawer-backdrop |
rgb(0 0 0 / 0.45) |
--pjx-drawer-header-bg |
var(--surface-alt) |
--pjx-drawer-header-sep |
var(--border) |
--pjx-drawer-footer-bg |
var(--surface-alt) |
--pjx-drawer-footer-sep |
var(--border) |
--pjx-drawer-padding |
1rem |
--pjx-drawer-z |
250 |
PJXProgress tokens¶
| Token | Default |
|---|---|
--pjx-progress-height |
0.5rem |
--pjx-progress-radius |
var(--radius-full) |
--pjx-progress-track |
var(--surface-alt) |
--pjx-progress-fill |
var(--brand) |
--pjx-progress-indeterminate-speed |
1.2s |
PJXSkeleton tokens¶
| Token | Default |
|---|---|
--pjx-skeleton-bg |
var(--surface-alt) |
--pjx-skeleton-shine |
color-mix(in srgb, var(--text) 8%, var(--surface-alt)) |
--pjx-skeleton-line-height |
0.65rem |
--pjx-skeleton-line-gap |
0.5rem |
--pjx-skeleton-circle-size |
2.5rem |
--pjx-skeleton-rect-height |
6rem |
--pjx-skeleton-rect-radius |
var(--radius-md) |
--pjx-skeleton-duration |
1.2s |
PJXEmptyState tokens¶
| Token | Default |
|---|---|
--pjx-empty-state-padding |
2rem 1.5rem |
--pjx-empty-state-max-width |
28rem |
--pjx-empty-state-title-size |
var(--font-size-md) |
--pjx-empty-state-desc-size |
var(--font-size-sm) |
--pjx-empty-state-title-color |
var(--text) |
--pjx-empty-state-desc-color |
var(--text-muted) |
--pjx-empty-state-gap |
0.5rem |
--pjx-empty-state-actions-gap |
0.5rem |
PJXDivider tokens¶
| Token | Default |
|---|---|
--pjx-divider-color |
var(--border) |
--pjx-divider-thickness |
1px |
--pjx-divider-gap |
0.75rem |
--pjx-divider-label-color |
var(--text-muted) |
--pjx-divider-label-size |
var(--font-size-sm) |
PJXSpinner tokens¶
| Token | Default |
|---|---|
--pjx-spinner-sm |
1.25rem |
--pjx-spinner-md |
2rem |
--pjx-spinner-lg |
2.75rem |
--pjx-spinner-track |
color-mix(in srgb, var(--text-muted) 35%, transparent) |
--pjx-spinner-accent |
var(--brand) |
PJXAvatar tokens¶
| Token | Default |
|---|---|
--pjx-avatar-sm |
2rem |
--pjx-avatar-md |
2.5rem |
--pjx-avatar-lg |
3.25rem |
--pjx-avatar-bg |
var(--surface-alt) |
--pjx-avatar-fg |
var(--text-muted) |
--pjx-avatar-border |
var(--border) |
PJXCard tokens¶
| Token | Default |
|---|---|
--pjx-card-bg |
var(--surface-alt) |
--pjx-card-border |
var(--border) |
--pjx-card-radius |
var(--radius-md) |
--pjx-card-title-color |
var(--text) |
--pjx-card-padding |
var(--space-4, 1rem) |
PJXBreadcrumb tokens¶
| Token | Default |
|---|---|
--pjx-breadcrumb-sep |
"/" |
--pjx-breadcrumb-link-color |
var(--brand) |
--pjx-breadcrumb-current-color |
var(--text) |
PJXTabGroup tokens¶
| Token | Default |
|---|---|
--pjx-tab-group-border |
var(--border) |
--pjx-tab-group-bg |
var(--surface-alt) |
--pjx-tab-group-tab-active-fg |
var(--text) |
--pjx-tab-group-tab-active-bg |
color-mix(in srgb, var(--surface) 55%, var(--surface-alt)) |
--pjx-tab-group-tab-active-border |
var(--brand) |
--pjx-tab-group-panel-bg |
var(--surface) |
PJXConfirmDialog tokens¶
| Token | Default |
|---|---|
--pjx-confirm-dialog-bg |
var(--surface) |
--pjx-confirm-dialog-border |
var(--border) |
--pjx-confirm-dialog-radius |
var(--radius-md) |
--pjx-confirm-dialog-shadow |
var(--shadow-md) |
--pjx-confirm-dialog-backdrop |
rgb(0 0 0 / 0.5) |
--pjx-confirm-dialog-danger |
#b3261e |
PJXPromptDialog tokens¶
| Token | Default |
|---|---|
--pjx-prompt-dialog-bg |
var(--surface) |
--pjx-prompt-dialog-border |
var(--border) |
--pjx-prompt-dialog-radius |
var(--radius-md) |
--pjx-prompt-dialog-shadow |
var(--shadow-md) |
--pjx-prompt-dialog-backdrop |
rgb(0 0 0 / 0.5) |
PJXToastHost tokens¶
| Token | Default |
|---|---|
--pjx-toast-bg |
var(--surface) |
--pjx-toast-border |
var(--border) |
--pjx-toast-radius |
var(--radius-md) |
--pjx-toast-shadow |
var(--shadow-md) |
--pjx-toast-gap |
0.5rem |
--pjx-toast-z |
1000 |
--pjx-toast-info |
var(--brand, #5c8fa8) |
--pjx-toast-success |
#3e7d4f |
--pjx-toast-warning |
#b07415 |
--pjx-toast-error |
#b3261e |
PJXAvatarStack tokens¶
| Token | Default |
|---|---|
--pjx-avatar-stack-overlap |
-0.5rem |
--pjx-avatar-stack-ring |
var(--surface) |
PJXPageLoader tokens¶
| Token | Default |
|---|---|
--pjx-page-loader-backdrop |
rgb(0 0 0 / 0.15) |
--pjx-page-loader-z |
9999 |
--pjx-page-loader-size |
2rem |
PJXChipInput tokens¶
| Token | Default |
|---|---|
--pjx-chip-input-gap |
0.375rem |
--pjx-chip-input-chip-bg |
var(--surface-alt) |
--pjx-chip-input-chip-fg |
var(--text) |
--pjx-chip-input-chip-radius |
var(--radius-full) |
--pjx-chip-input-border |
var(--border) |
--pjx-chip-input-focus |
var(--border-focus, var(--border)) |
--pjx-chip-input-radius |
var(--radius-md) |
--pjx-chip-input-padding |
0.375rem 0.5rem |
PJXFormField tokens¶
| Token | Default |
|---|---|
--pjx-form-field-gap |
0.375rem |
--pjx-form-field-label-size |
0.875em |
--pjx-form-field-label-color |
var(--text) |
--pjx-form-field-help-color |
var(--text-muted) |
--pjx-form-field-error |
#b3261e |
PJXToggleSwitch tokens¶
| Token | Default |
|---|---|
--pjx-toggle-switch-width |
2.75rem |
--pjx-toggle-switch-height |
1.5rem |
--pjx-toggle-switch-track |
var(--border) |
--pjx-toggle-switch-track-on |
var(--brand) |
--pjx-toggle-switch-thumb |
#fff |
--pjx-toggle-switch-radius |
var(--radius-full) |
--pjx-toggle-switch-gap |
0.5rem |
PJXSegmentedControl tokens¶
| Token | Default |
|---|---|
--pjx-segmented-control-bg |
var(--surface-alt) |
--pjx-segmented-control-border |
var(--border) |
--pjx-segmented-control-radius |
var(--radius-full) |
--pjx-segmented-control-gap |
0.25rem |
--pjx-segmented-control-padding |
0.25rem |
--pjx-segmented-control-active-bg |
var(--surface) |
--pjx-segmented-control-active-fg |
var(--text) |
--pjx-segmented-control-active-shadow |
0 1px 3px rgb(0 0 0 / 0.12) |
--pjx-segmented-control-fg |
var(--text-muted) |
--pjx-segmented-control-focus |
var(--border-focus, var(--brand)) |
PJXPasswordInput tokens¶
| Token | Default |
|---|---|
--pjx-password-input-border |
var(--border) |
--pjx-password-input-radius |
var(--radius-md) |
--pjx-password-input-focus |
var(--border-focus, var(--brand)) |
--pjx-password-input-toggle-color |
var(--text-muted) |
--pjx-password-input-eye-size |
1.25rem |