Theming guide
A theme is a plain CSS file. Anything goes: you can restyle every element the app renders. This page describes the theming API, version 1: the parts the app promises to keep stable. The built-in themes use only this API. A theme that relies on anything else (other class names, element structure) works today but may break with any release.
Start from the starter template, then load it as your own theme (Theme → Use your own CSS file) or, as host, as the room's theme (Settings → Room theme → Upload CSS).
How themes are loaded
The app's styles live in cascade layers, declared in this order:
@layer locked, base, theme.room, theme.personal; A room theme is imported into
theme.room, a personal theme into theme.personal, so a personal
theme overrides the room's, and both override the app's base styles without
needing higher specificity. The whole file goes into its layer, including any
@layer blocks inside it.
A few rules are locked and cannot be overridden, not even with
!important: the “Use default theme” button (.itd-theme-escape), so
everyone can always get back to the default look, and the display,
visibility and opacity of html, body and
itd-app, which hold it.
A room theme and a personal theme are active at the same time, and
@keyframes names are global: when both define the same name, one silently
replaces the other. Prefix your keyframes with your theme's name, for example
@keyframes my-theme-blink. The itd- prefix is the app's own: its
keyframes are named itd-…, so a theme must not use it.
Design tokens
Set these custom properties on :root. The base styles use them everywhere.
| Property | Used for |
|---|---|
color-scheme |
Not a token, but set it: light, dark or
light dark (default, follows the system). Default colors use
light-dark().
|
--itd-font |
Font family of everything |
--itd-bg |
Page background |
--itd-surface |
Inputs, buttons, menus, dialogs, bottom sheets. Keep it opaque. |
--itd-panel |
The columns and the stage (default: --itd-surface). May be translucent, to
let the page background show through.
|
--itd-backdrop |
The dimmed page behind dialogs and bottom sheets |
--itd-fg |
Text |
--itd-muted |
Secondary text, badges |
--itd-border |
Borders and separators |
--itd-accent, --itd-accent-fg |
Primary buttons, active item, chart bars; text on accent |
--itd-warning |
Warnings, an expired timer, the highest vote |
--itd-accent-soft |
Tinted backgrounds: the active item, the final-estimate bar, chips |
--itd-success, --itd-success-soft |
Estimated items, voted, consensus |
--itd-caution, --itd-caution-soft |
Incomplete items, warnings, a wide spread |
--itd-danger, --itd-danger-fg |
Errors and destructive actions (Delete); text on a danger background |
--itd-outline |
Borders of fields (at least 3:1 against the surface) |
--itd-shadow, --itd-shadow-raised |
Panels; the stage, menus, dialogs and the picked card |
--itd-card-bg, --itd-card-fg, --itd-card-border
|
Voting cards |
--itd-card-selected-bg, --itd-card-selected-fg |
The card you picked |
--itd-radius |
Corner radius |
--itd-radius-pill |
Fully rounded shapes: chips, badges, the timer, progress bars (default: 999px; 0 squares them) |
--itd-focus |
The keyboard focus ring (default: --itd-accent) |
--itd-gap |
Spacing between and inside panels |
Stable class names
Every class that is part of the API starts with itd-. Classes without the
prefix are the app's own and may change with any release; a theme should not rely on them.
| Area | Classes |
|---|---|
| Start page |
.itd-start .itd-start-header .itd-start-tagline
.itd-start-cards .itd-start-create
.itd-start-about .itd-start-steps
.itd-start-features .itd-start-faq
(what the app is, below the tools)
.itd-start-pick .itd-input-row
.itd-start-snapshot .itd-kept-rooms
.itd-kept-room .itd-kept-room-name
.itd-kept-room-meta
|
| Room frame |
.itd-room .itd-room-header .itd-room-title
.itd-room-name .itd-room-actions .itd-room-layout
.itd-notices .itd-warning .itd-error
.itd-succession-note .itd-stranded (others are in the room but
none can be reached)
|
| Header actions |
.itd-download-snapshot .itd-load-snapshot
.itd-copy-link .itd-open-settings
.itd-profile-button .itd-profile-name
#profile (the profile menu) .itd-leave-room (its way back to
the start page) .itd-menu
|
| Name dialog |
.itd-name-dialog .itd-name-prompt
.itd-name-prompt-title
.itd-name-prompt-hint
|
| Items |
.itd-items .itd-items-header .itd-item-list
.itd-item .itd-item-drag-handle .itd-item-select
.itd-item-main .itd-item-text .itd-item-status
.itd-item-meta .itd-item-estimate
.itd-item-actions .itd-item-menu-button
.itd-item-menu .itd-items-progress
.itd-items-total .itd-add-items-panel
.itd-add-items
|
| Stage |
.itd-round .itd-round-header .itd-round-phase
.itd-round-item .itd-round-hint .itd-round-empty
.itd-action-band (the host's bar at the bottom: start voting, reveal now,
agreed estimate; it sticks to the bottom while the stage scrolls under it, so give it an
opaque background)
|
| Voting |
.itd-start-voting .itd-timer-field
.itd-timer-choices .itd-timer-preset
.itd-round-status .itd-progress
.itd-progress-text .itd-progress-line
.itd-voter-avatars .itd-voter-avatar .itd-timer
.itd-countdown .itd-countdown-number
.itd-countdown-label .itd-cards .itd-card
.itd-voting-actions .itd-text-answer
.itd-withdraw .itd-reveal-now
|
| Reveal |
.itd-results .itd-verdict .itd-verdict-text
.itd-verdict-title
.itd-votes .itd-vote .itd-vote-value
.itd-vote-voter .itd-vote-extreme .itd-stats
.itd-stat .itd-stat-label .itd-stat-value
.itd-last-round .itd-disclosure-title (the summary of a
foldable section)
|
| Agreed estimate |
.itd-final-estimate-band .itd-final-estimate
.itd-estimate-text .itd-estimate-label
.itd-estimate-hint .itd-estimate-links
.itd-estimate-input .itd-estimate-saved-value
.itd-estimate-value .itd-save-estimate
.itd-use-suggestion .itd-vote-again
.itd-revote-confirm .itd-discard-votes
.itd-keep-votes · for voters: .itd-final-estimate-value
|
| Participants |
.itd-participants .itd-participant
.itd-participant-info .itd-participant-name
.itd-participant-badges .itd-badge
.itd-you-marker .itd-vote-status-cell
.itd-vote-status .itd-avatar .itd-observers-title
.itd-observers (the observers, listed after the voters)
|
| Bottom sheets (narrow screens) |
.itd-sheet .itd-items-sheet .itd-people-sheet
.itd-sheet-handle .itd-sheet-buttons
.itd-sheet-button .itd-items-button
.itd-people-button .itd-sheet-badge
|
| Dialogs |
.itd-settings .itd-settings-form
.itd-settings-section .itd-deck-preview
.itd-deck-cards .itd-deck-card .itd-room-theme
.itd-transfer-host .itd-theme-dialog
.itd-theme-tiles .itd-theme-tile
.itd-theme-swatch .itd-theme-swatch-card
.itd-theme-swatch-accent .itd-theme-tile-name
.itd-dialog-body .itd-dialog-actions
.itd-avatar-colors (the profile menu's color pickers)
.itd-avatar-seed-toggle .itd-avatar-seed (its avatar, which
shows the seed field)
|
| Buttons and bits |
.itd-primary .itd-tonal .itd-chip
.itd-link-button .itd-icon-button .itd-icon
.itd-count .itd-hint .itd-visually-hidden
.itd-button (a label that looks like a button: the file pickers)
.itd-dismiss-error
|
Besides the classes, these elements are part of the API: the page (html,
body), links (a), the form controls (button,
input, select, textarea) and the modal dialogs
(dialog). Panels are section elements; headings are
h1–h3. Anything else inside a component is reached through its
class. Below 1024px wide the layout collapses: the item list and the participants become
bottom sheets (.itd-sheet) opened by .itd-sheet-buttons.
State in data attributes
| Element | Attribute and values |
|---|---|
.itd-item |
data-status: open, estimated, incomplete, rejected ·
data-active: true, false · data-dragging while dragged (and on
.itd-item-list) · custom property --i: its position in the
whole list
|
.itd-sheet |
data-dragging while its handle is dragged (the sheet follows the finger)
|
.itd-item-select |
aria-current=true on the active item |
.itd-round |
data-phase: none, discussing, voting, revealed ·
data-counting-down: true, false
|
.itd-round-phase |
data-phase: discussing, voting, revealing, revealed |
.itd-card |
data-selected: true, false · data-value: its label · custom
property --i: its position in the deck
|
.itd-timer |
data-ending (last 15 s), data-expired: true, false |
.itd-timer-preset |
aria-pressed: true for the chosen preset |
.itd-voter-avatar |
data-voted: true, false |
.itd-progress-line |
custom property --done: 0–1 |
.itd-results |
data-animate: true for the round just revealed, false for an earlier round
shown again (animate only the first)
|
.itd-final-estimate-value |
data-status: the item's status; incomplete and rejected keep an estimate
that no longer counts
|
.itd-verdict |
data-verdict: consensus (above the votes, which then lie in one pile), wide
(under the statistics)
|
.itd-vote |
data-kind: card, text · data-number: on a text answer that is
a number · data-extreme: min, max, none · custom property --i:
its index, for staggered animation · custom property --rank: 0 (lowest) to
1 (highest) by value (in a deck without values, by the card's place in the deck), 0.5
when all are equal; unset for votes that don't count
|
.itd-vote-value |
data-value: the vote as shown |
.itd-countdown |
data-count: the seconds left (3, 2, 1), changing every second |
.itd-stat |
data-stat: min, max, mean, median · custom property --i: 0–3
in that order
|
.itd-final-estimate-band |
data-state: editing, saved |
.itd-participant |
data-connected: true, false · data-reachability: self,
connecting, connected, unreachable · data-role: voter, observer ·
data-vote: none, voted, pending · custom property --i: its
position in the list
|
.itd-badge |
data-badge=host on the host badge |
.itd-copy-link |
data-copied for two seconds after copying |
.itd-sheet |
:popover-open while open as a bottom sheet · custom property
--sheet-open: 1→0 while dragged down (used by its backdrop)
|
.itd-theme-tile |
data-theme-id: the theme's ID, or room |
html |
data-room-theme, data-personal-theme: the active theme's ID,
or custom
|
Versioning
Version 1. Removing or renaming anything listed here makes a new version, announced in the app’s release notes. Additions don’t.