Tooltip
Use CTooltip for brief descriptions that appear on keyboard focus or fine-pointer hover. It keeps focus on the activator, crosses the pointer gap, and enters the browser top layer without moving its DOM.
Tooltip at a glance
Focus or hover each Button. The first hover waits briefly; nearby Tooltips then open immediately.
Show code
import citry_ui
from citry import Component, citry
citry.register_library(citry_ui)
class TooltipAtAGlance(Component):
template = """
<section class="tooltip-sampler">
<c-CTooltip text="Ocean world beneath fractured ice" placement="top-start">
<c-fill name="activator" data="{ activator_attrs }">
<c-CButton c-attrs="activator_attrs">Europa</c-CButton>
</c-fill>
</c-CTooltip>
<c-CTooltip text="Bright plumes rise above the south pole" placement="top">
<c-fill name="activator" data="{ activator_attrs }">
<c-CButton variant="outline" c-attrs="activator_attrs">Enceladus</c-CButton>
</c-fill>
</c-CTooltip>
<c-CTooltip text="Dense nitrogen skies conceal methane lakes" placement="top-end">
<c-fill name="activator" data="{ activator_attrs }">
<c-CButton variant="ghost" c-attrs="activator_attrs">Titan</c-CButton>
</c-fill>
</c-CTooltip>
</section>
"""
css = """
:where(.tooltip-sampler) {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 0.75rem;
min-block-size: 12rem;
padding-block: 2rem;
}
"""
preview = TooltipAtAGlance()
preview # noqa: B018
Describe one activator
Provide concise text and spread activator_attrs onto exactly one enabled, focusable element.
Show code
import citry_ui
from citry import Component, citry
citry.register_library(citry_ui)
class MoonLabels(Component):
template = """
<section class="moon-labels">
<c-CTooltip text="Inspect Europa's fractured water-ice crust">
<c-fill name="activator" data="{ activator_attrs }">
<c-CButton c-attrs="activator_attrs">Europa</c-CButton>
</c-fill>
</c-CTooltip>
<c-CTooltip text="Compare Ganymede's ancient grooved terrain">
<c-fill name="activator" data="{ activator_attrs }">
<a id="ganymede" class="moon-link" href="#ganymede" c-bind="activator_attrs">
Ganymede
</a>
</c-fill>
</c-CTooltip>
<c-CTooltip text="Filter observations recorded near Callisto">
<c-fill name="activator" data="{ activator_attrs }">
<button class="moon-icon" type="button" aria-label="Filter Callisto" c-bind="activator_attrs">
◌
</button>
</c-fill>
</c-CTooltip>
</section>
"""
css = """
:where(.moon-labels) {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 1rem;
min-block-size: 10rem;
padding-block: 2rem;
}
:where(.moon-link) {
color: light-dark(#175cd3, #84adff);
font-weight: 700;
}
:where(.moon-icon) {
display: grid;
place-items: center;
inline-size: 2.75rem;
block-size: 2.75rem;
border: 1px solid color-mix(in srgb, CanvasText 24%, transparent);
border-radius: 50%;
background: Canvas;
color: CanvasText;
font-size: 1.5rem;
}
"""
preview = MoonLabels()
preview # noqa: B018
<c-CTooltip text="Inspect Europa's fractured ice">
<c-fill name="activator" data="{ activator_attrs }">
<c-CButton c-attrs="activator_attrs">
Europa
</c-CButton>
</c-fill>
</c-CTooltip>
text supplements the activator's accessible name. It does not replace one. An icon-only Button still needs its own accessible name.
The activator may be a Button, link with href, form control, or another element with a real keyboard focus path. Tooltip rejects disabled and nonfocusable activators; persistent text is clearer for unavailable controls.
Add simple formatting
Omit text and supply the default fill for static, noninteractive formatting.
Show code
import citry_ui
from citry import Component, citry
citry.register_library(citry_ui)
class FormattedTooltip(Component):
template = """
<section class="formatted-tooltip">
<c-CTooltip placement="bottom">
<c-fill name="activator" data="{ activator_attrs }">
<c-CButton variant="outline" c-attrs="activator_attrs">
Europa orbit
</c-CButton>
</c-fill>
<c-fill name="default">
Orbital period: <strong>3.55 Earth days</strong>
</c-fill>
</c-CTooltip>
</section>
"""
css = """
:where(.formatted-tooltip) {
display: grid;
place-items: center;
min-block-size: 12rem;
}
"""
preview = FormattedTooltip()
preview # noqa: B018
<c-fill name="default">
Orbital period: <strong>3.55 Earth days</strong>
</c-fill>
Do not put links, Buttons, form controls, editable content, widgets, or nested Tooltips in the surface. Use CPopover for interactive content. Keep essential instructions and validation feedback persistently visible.
Update text in the browser
Client inputs are passed through $c-props="{...}". Client text safely updates a Tooltip authored with the server text input.
Show code
import citry_ui
from citry import Component, citry
citry.register_library(citry_ui)
class LiveTooltipText(Component):
template = """
<section class="live-tooltip" x-data="{ unit: 'kilometres' }">
<c-CTooltip
text="Europa is 3,122 kilometres wide"
$c-props="{
text: unit === 'kilometres'
? 'Europa is 3,122 kilometres wide'
: 'Europa is 1,940 miles wide',
}"
>
<c-fill name="activator" data="{ activator_attrs }">
<c-CButton c-attrs="activator_attrs">Europa diameter</c-CButton>
</c-fill>
</c-CTooltip>
<label>
Units
<select x-model="unit">
<option value="kilometres">Kilometres</option>
<option value="miles">Miles</option>
</select>
</label>
</section>
"""
css = """
:where(.live-tooltip) {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 1rem;
min-block-size: 11rem;
padding-block: 2rem;
}
:where(.live-tooltip label) {
display: grid;
gap: 0.25rem;
font-size: 0.875rem;
}
"""
preview = LiveTooltipText()
preview # noqa: B018
Use the default fill for server-authored formatting; client text does not replace arbitrary slotted markup.
Tune hover timing
Focus always opens immediately. delay affects only the first fine-pointer hover. close_delay keeps a bridge open while the pointer moves from the activator onto the Tooltip.
Show code
import citry_ui
from citry import Component, citry
citry.register_library(citry_ui)
class TooltipTiming(Component):
template = """
<section class="tooltip-timing">
<c-CTooltip text="Opens after the standard 600 ms hover delay">
<c-fill name="activator" data="{ activator_attrs }">
<c-CButton c-attrs="activator_attrs">Standard delay</c-CButton>
</c-fill>
</c-CTooltip>
<c-CTooltip text="Opens without an initial hover delay" c-delay="0">
<c-fill name="activator" data="{ activator_attrs }">
<c-CButton variant="outline" c-attrs="activator_attrs">Immediate</c-CButton>
</c-fill>
</c-CTooltip>
<c-CTooltip text="A longer bridge makes the surface easier to reach" c-close_delay="500">
<c-fill name="activator" data="{ activator_attrs }">
<c-CButton variant="ghost" c-attrs="activator_attrs">Long bridge</c-CButton>
</c-fill>
</c-CTooltip>
</section>
"""
css = """
:where(.tooltip-timing) {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 0.75rem;
min-block-size: 12rem;
padding-block: 2rem;
}
"""
preview = TooltipTiming()
preview # noqa: B018
Once one Tooltip opens, nearby Tooltips skip the first-hover delay until a short cooldown ends. No provider or group component is required.
Control visibility
Supply a client Boolean open to control visual visibility. onOpenChange reports requests; update the owner value to accept one or leave it unchanged to decline it.
Show code
import citry_ui
from citry import Component, citry
citry.register_library(citry_ui)
class ControlledTooltip(Component):
template = """
<section class="controlled-tooltip" x-data="{ open: false, locked: false, reason: 'none' }">
<c-CTooltip
text="Controlled description for the Europa archive"
$c-props="{
open,
onOpenChange: (nextOpen, detail) => {
reason = detail.reason;
if (!locked) open = nextOpen;
},
}"
>
<c-fill name="activator" data="{ activator_attrs }">
<c-CButton c-attrs="activator_attrs">Europa archive</c-CButton>
</c-fill>
</c-CTooltip>
<label>
<input type="checkbox" x-model="locked" />
Decline requests
</label>
<output x-text="`Last request: ${reason}`"></output>
</section>
"""
css = """
:where(.controlled-tooltip) {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 1rem;
min-block-size: 12rem;
padding-block: 2rem;
}
:where(.controlled-tooltip output) {
flex-basis: 100%;
color: color-mix(in srgb, CanvasText 72%, transparent);
}
"""
preview = ControlledTooltip()
preview # noqa: B018
<c-CTooltip
text="Europa has a hidden ocean"
$c-props="{
open,
onOpenChange: (nextOpen) => open = nextOpen,
}"
>
...
</c-CTooltip>
Without client open, Tooltip commits requests itself and then notifies. Passing null or omitting the client value releases control without resetting the current state. Owner commits do not notify. Callback detail reports the interaction reason, controlled ownership, browser source, and whether an ancestor or modal safety rule forced the Tooltip closed.
Place the surface
Server inputs are passed through <c-CTooltip ... /> attributes or a CTooltip(...) composition call. placement accepts logical top and bottom start, center, and end positions. The browser may flip the surface near an edge.
Customize example
Show code
import citry_ui
from citry import Component, citry
citry.register_library(citry_ui)
class TooltipPlacements(Component):
template = """
<section
class="tooltip-placement"
x-data="{ placement: 'top' }"
@citry-ui-preview-controls.window="Object.assign($data, $event.detail)"
>
<c-CTooltip
text="The browser may flip this surface near an edge"
$c-props="{ placement }"
>
<c-fill name="activator" data="{ activator_attrs }">
<c-CButton c-attrs="activator_attrs">Place orbital note</c-CButton>
</c-fill>
</c-CTooltip>
</section>
"""
css = """
:where(.tooltip-placement) {
display: grid;
place-items: center;
min-block-size: 20rem;
}
"""
preview_controls = (
{
"name": "placement",
"label": "Placement",
"type": "select",
"default": "top",
"options": (
("top-start", "Top start"),
("top", "Top"),
("top-end", "Top end"),
("bottom-start", "Bottom start"),
("bottom", "Bottom"),
("bottom-end", "Bottom end"),
),
},
)
preview = TooltipPlacements()
preview # noqa: B018
Start and end follow text direction. Change the activator gap with --cui-tooltip-offset; change line length with --cui-tooltip-max-inline-size.
Dismiss and revisit
Escape closes only the top Tooltip and leaves focus on the activator. Pressing an open activator also dismisses its Tooltip without canceling the native action. It stays closed until focus and pointer both leave, so it does not immediately reopen.
Show code
import citry_ui
from citry import Component, citry
citry.register_library(citry_ui)
class TooltipDismissal(Component):
template = """
<section class="tooltip-dismissal">
<p>Focus the Button, press Escape, then move focus away and return.</p>
<c-CTooltip text="Escape closes this description without moving focus">
<c-fill name="activator" data="{ activator_attrs }">
<c-CButton c-attrs="activator_attrs">Europa telemetry</c-CButton>
</c-fill>
</c-CTooltip>
<c-CButton variant="outline">Next observation</c-CButton>
</section>
"""
css = """
:where(.tooltip-dismissal) {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 0.75rem;
min-block-size: 12rem;
padding-block: 2rem;
}
:where(.tooltip-dismissal p) {
flex-basis: 100%;
margin: 0;
}
"""
preview = TooltipDismissal()
preview # noqa: B018
Touch activation does not show a visual Tooltip. The interface must remain understandable without one.
Theme Tooltip
Set public --cui-tooltip-* variables on an ancestor or one surface.
Show code
import citry_ui
from citry import Component, citry
citry.register_library(citry_ui)
class CustomizedTooltip(Component):
template = """
<section class="custom-tooltips">
<c-CTooltip text="Charged particles paint green arcs" class_="aurora-tooltip">
<c-fill name="activator" data="{ activator_attrs }">
<c-CButton c-attrs="activator_attrs">Auroral oval</c-CButton>
</c-fill>
</c-CTooltip>
<c-CTooltip text="Ancient pale terrain surrounds dark maria" class_="lunar-tooltip">
<c-fill name="activator" data="{ activator_attrs }">
<c-CButton variant="outline" c-attrs="activator_attrs">Lunar highlands</c-CButton>
</c-fill>
</c-CTooltip>
</section>
"""
css = """
:where(.custom-tooltips) {
display: flex;
flex-wrap: wrap;
gap: 0.75rem;
min-block-size: 12rem;
padding-block: 2rem;
}
:where(.aurora-tooltip) {
--cui-tooltip-background: light-dark(#064e3b, #d1fae5);
--cui-tooltip-foreground: light-dark(#ecfdf5, #052e2b);
--cui-tooltip-border-color: light-dark(#34d399, #6ee7b7);
--cui-tooltip-radius: 1rem;
}
:where(.lunar-tooltip) {
--cui-tooltip-background: light-dark(#334155, #e2e8f0);
--cui-tooltip-foreground: light-dark(#f8fafc, #172033);
--cui-tooltip-border-color: light-dark(#94a3b8, #64748b);
--cui-tooltip-shadow: 0 0.75rem 2rem rgb(15 23 42 / 30%);
}
"""
preview = CustomizedTooltip()
preview # noqa: B018
.aurora-tooltip {
--cui-tooltip-background: light-dark(#064e3b, #d1fae5);
--cui-tooltip-foreground: light-dark(#ecfdf5, #052e2b);
--cui-tooltip-border-color: light-dark(#34d399, #6ee7b7);
--cui-tooltip-radius: 1rem;
}
class_, style, and attrs target the Tooltip surface. The activator stays owned by its authored component. Unlayered consumer CSS overrides Citry UI defaults; named layers follow the site-wide layer-order contract.
The documented variables, selector, and reflected attributes are public CSS API. .cui-* classes, --_cui-* variables, host markup, initialization markers, and anchor names are private.
Support long text, RTL, and zoom
Show code
import citry_ui
from citry import Component, citry
citry.register_library(citry_ui)
class ResponsiveTooltipText(Component):
template = """
<section class="responsive-tooltips" dir="rtl">
<c-CTooltip
text="أوروبا قمر جليدي يخفي محيطًا عالميًا تحت قشرته المتشققة"
placement="bottom-start"
>
<c-fill name="activator" data="{ activator_attrs }">
<c-CButton c-attrs="activator_attrs">أوروبا</c-CButton>
</c-fill>
</c-CTooltip>
<c-CTooltip
text="Averylongunbrokenastronomicalcatalogidentifierwrapswithoutwideningthepage"
placement="bottom-end"
>
<c-fill name="activator" data="{ activator_attrs }">
<c-CButton variant="outline" c-attrs="activator_attrs">Catalog ID</c-CButton>
</c-fill>
</c-CTooltip>
</section>
"""
css = """
:where(.responsive-tooltips) {
display: flex;
flex-wrap: wrap;
gap: 0.75rem;
min-block-size: 12rem;
padding-block: 2rem;
}
:where(.responsive-tooltips [data-citry-ui-part="tooltip"]) {
--cui-tooltip-max-inline-size: 13rem;
}
"""
preview = ResponsiveTooltipText()
preview # noqa: B018
Logical placement, a viewport-safe maximum, and aggressive wrapping keep text reachable at narrow widths and high zoom. The surface follows surrounding light/dark scope even in the top layer. Forced colors preserve its boundary; reduced motion removes transitions; print omits visual Tooltips.
Without JavaScript, an initially closed Tooltip remains hidden. An initially open Tooltip renders readable text in document flow, then activation upgrades it to the top layer.
Choose the right surface
- Use
CPopoverfor links, controls, forms, or other interactive content. - Use
CAlertfor persistent status or feedback. - Use a Field description for instructions tied to a form control.
- Use visible prose when the information is essential to completing a task.
API reference
Inputs
CTooltip server inputs
Server inputs are passed in a template through <c-CTooltip ... /> or in Python through CTooltip(...).
| Input | Type | Default | Effect |
|---|---|---|---|
id | str | None | generated | Sets the Tooltip identity and activator description relationship. |
text | str | None | None | Supplies concise plain text. Use either text or the default fill, never both. |
open | bool | False | Sets the server-visible initial state and uncontrolled fallback. |
disabled | bool | False | Suppresses visual opening without changing the activator itself. |
delay | int | 600 | Sets the first fine-pointer hover delay in milliseconds from 0 through 60000. Focus remains immediate. |
close_delay | int | 100 | Sets the pointer bridge delay in milliseconds from 0 through 60000. |
placement | "top-start" | "top" | "top-end" | "bottom-start" | "bottom" | "bottom-end" (CTooltipPlacement) | "top" | Sets the preferred logical placement. Collision fallback may choose another rendered side. |
class_ | str | Mapping[str, bool] | Sequence[CClassValue] | None (CClassValue) | None | Adds surface classes and merges them with attrs. |
style | str | Mapping[str, str | int | float | bool | None] | Sequence[CStyleValue] | None (CStyleValue) | None | Adds surface inline styles and merges them with attrs; Citry retains anchor ownership. |
attrs | Mapping[str, object] | None | None | Adds allowed native, Alpine, and data attributes to the Tooltip surface. Owned presence, semantics, focus, and relationships are rejected. |
CTooltip client inputs
Client inputs are passed in the browser through the $c-props="{ ... }" attribute on <c-CTooltip />.
| Input | Type | Omitted behavior | Effect |
|---|---|---|---|
open | boolean | null | Releases control and preserves the current committed state. null has the same effect. | Controls visual visibility while supplied as a Boolean. Disabled still dominates. |
text | string | Uses server text. | Replaces plain text in text mode. Supplying it to a slotted Tooltip is invalid. |
disabled | boolean | Uses the server input. | Controls Tooltip-local availability. |
delay | integer | Uses the server input. | Controls future first-hover delay from 0 through 60000 milliseconds. |
closeDelay | integer | Uses the server input. | Controls the pointer bridge from 0 through 60000 milliseconds. |
placement | "top-start" | "top" | "top-end" | "bottom-start" | "bottom" | "bottom-end" (CTooltipPlacement) | Uses the server input. | Controls requested placement and data-placement. |
onOpenChange | function | Does not notify a component callback. | Receives hover, focus, dismissal, peer, press, and external-native visibility requests. |
Slots
Slots are passed as nested content or <c-fill> tags in a template, or through the slots={...} argument in Python.
CTooltip slots
| Slot | Required | Data | Fallback |
|---|---|---|---|
activator | yes | {activator_attrs: dict[str, object], tooltip_id: str} (CTooltipActivatorSlotData) | none |
default | no | {} (CTooltipDefaultSlotData) | Escaped text. Required when text is omitted and forbidden when it is supplied. |
Events
Component events are callback inputs supplied through $c-props. Native browser events remain available through Alpine @... attributes.
CTooltip events
| Event | Signature | Trigger and timing | Detail | Controlled and cancellation behavior |
|---|---|---|---|---|
onOpenChange | (requestedOpen: boolean, detail: CTooltipOpenChangeDetail) => void (CTooltipOpenChangeDetail) | Hover, focus, pointer departure, blur, Escape, trigger press, a peer Tooltip, or external native visibility requests another state. | {reason: "hover" | "focus" | "pointer-leave" | "blur" | "escape" | "press" | "peer" | "native" | "ancestor" | "modal", controlled: boolean, forced: boolean, source: EventTarget | null} (CTooltipOpenChangeDetail) | Uncontrolled requests commit before notification. Controlled requests wait for the owner. Owner commits do not notify. |
Methods
-
CSS
CSS variables to theme the components. Set them on an ancestor or the component itself.
CTooltip CSS variables
Apply these variables to CTooltip or one of its ancestors.
| Variable | Type | Purpose | Default |
|---|---|---|---|
--cui-tooltip-background | color | Surface background. | Scheme-aware dark/light inverse surface. |
--cui-tooltip-foreground | color | Surface text. | Scheme-aware inverse foreground. |
--cui-tooltip-border-color | color | Surface boundary. | Subtle currentColor mix. |
--cui-tooltip-border-width | length | Boundary width. | 1px |
--cui-tooltip-radius | length | Surface corner radius. | 0.375rem |
--cui-tooltip-shadow | shadow | Top-layer elevation. | 0 0.5rem 1.25rem rgb(15 23 42 / 24%) |
--cui-tooltip-max-inline-size | length | Maximum text width. | 18rem |
--cui-tooltip-padding-block | length | Block-axis content padding. | 0.375rem |
--cui-tooltip-padding-inline | length | Inline-axis content padding. | 0.625rem |
--cui-tooltip-offset | length | Gap between activator and surface. | 0.375rem |
--cui-tooltip-duration | time | Entry and exit duration; reduced motion resolves to zero. | 100ms |
--cui-tooltip-easing | easing | Entry and exit easing. | 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.
CTooltip attributes
| Attribute | Element | Type | Meaning |
|---|---|---|---|
popover | Surface | "manual" | Uses native top-layer presence while Citry owns timing and dismissal. |
role | Surface | "tooltip" | Identifies the noninteractive description. |
data-open | Surface | present | absent | Mirrors logical visual visibility; absent during exit. |
data-placement | Surface | six placement strings (CTooltipPlacement) | Mirrors requested placement, not the collision fallback result. |
aria-describedby | Activator | IDREF list | Includes the Tooltip ID without replacing existing description relationships. |
Selectors
Selectors for the DOM nodes in the components that you can use for CSS, inspection, and testing.
CTooltip selectors
| Selector | Element | Purpose |
|---|---|---|
[data-citry-ui-part="tooltip"] | Surface | Semantic root, visual surface, 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] |
CTooltipPlacement | Literal["top-start", "top", "top-end", "bottom-start", "bottom", "bottom-end"] |
CTooltipActivatorSlotData
| Field | Type | Default | Meaning |
|---|---|---|---|
activator_attrs | dict[str, object] | - | Owned trigger marker, CSS anchor, and aria-describedby relationship. |
tooltip_id | str | - | Tooltip ID for composing additional activator description IDREFs. |
CTooltipDefaultSlotData
Empty dataclass: {}.
CTooltipOpenChangeDetail
| Field | Type | Default | Meaning |
|---|---|---|---|
reason | "hover" | "focus" | "pointer-leave" | "blur" | "escape" | "press" | "peer" | "native" | "ancestor" | "modal" | - | Source of the requested visibility change. |
controlled | boolean | - | Whether a valid client open Boolean currently owns state. |
forced | boolean | - | Whether structural or modal safety required the component to close regardless of controlled ownership. |
source | EventTarget | null | - | Browser source associated with the request. |
Translation keys
-