--- title: "Ui: conventions" description: "smabar plugin reference: Ui: conventions" canonical: "https://smabar.com/docs/plugin-ref-ui-conventions/" lang: en --- # Ui: conventions > smabar plugin reference: Ui: conventions Canonical HTML version: https://smabar.com/docs/plugin-ref-ui-conventions/ · Language: en · Publisher: smabar (https://smabar.com) --- [Build my plugin](https://smabar.com/build-my-plugin.md) · [All references](https://smabar.com/docs/plugin-ref-index.md) · [Install the ZIP](https://smabar.com/docs/install-plugin-zip.md) Source: smabar 1.5.0, revision `a019f2563965edc37a5e8d84742ea1ff2fa6f824`. [Export metadata and checksums](https://smabar.com/plugin-reference/export.json). This is an offline reference. MCP calls quoted below describe the connected workflow; use the linked documents and create local files instead. Live state, installed plugins, available providers and the user's active theme are unknown. Never claim a live check. Read with [styling](https://smabar.com/docs/plugin-ref-styling.md) and [examples](https://smabar.com/docs/plugin-ref-examples.md). ## marquee ```json { "attribute": "data-marquee", "usage": "Put data-marquee on a width-constrained element. The shell scrolls its content smoothly only while it overflows horizontally, pauses on hover, and disables motion for prefers-reduced-motion.", "example": "A status text that may be too long" } ``` ## badge ```json { "attribute": "data-badge", "usage": "Put data-badge=\"text\" on a tile root or any element for a top-right accent badge. An empty value renders a dot.", "example": "
Inbox
" } ``` ## carousel ```json { "attribute": "data-carousel", "usage": "Put data-carousel on a container; every direct child becomes one full-width scroll-snap slide. Touch, pen and mouse drag use pointer capture, trackpads scroll natively, and ArrowLeft/ArrowRight work when the carousel is focused. Optional data-carousel-interval=\"5000\" auto-advances and wraps every N ms (minimum 1500), pauses for pointer, drag or keyboard focus, and stays off under prefers-reduced-motion. Give the container an aria-label and every media slide useful alt text or visible text.", "example": "
\"Gallery\"Gallery
" } ``` ## tabs ```json { "attribute": "data-tabs", "usage": "Container with data-tabs: any element carrying data-tab=\"\" (a sb-tab button, or a whole sb-row so a list of items doubles as the tab strip) switches the panels carrying data-tab-panel=\"\" locally in the shell — no plugin round-trip. The first tab starts active unless one already carries sb-active; the shell toggles sb-active and panel visibility. data-action on the same element still fires: give the tab data-action=\"select\" and mark the chosen one sb-active on the next render, otherwise your next push resets the view to the first tab. Put the hidden attribute on every panel that is not active, so the markup is honest before the shell runs.", "example": "
Now content
Week content
" } ``` ## contextMenu ```json { "attribute": "data-context-items", "usage": "Custom right-click menus. Put data-context-items='' (single quotes outside, JSON inside) on ANY element of your HTML — tile, flyout or popup. The bar suppresses the webview's native menu everywhere, so this is the only menu the user sees. Selecting an entry reaches your plugin as a NORMAL action: the same plugin_action call a data-action click makes, with {action, value} — there is nothing new to implement, your existing action handler receives it. Entry fields: label (REQUIRED, plain TEXT inserted as text — you own your i18n; longer than 64 chars is truncated with an ellipsis), action (REQUIRED for a selectable entry, at most 64 chars), value (optional string payload, at most 512 chars), icon (optional, MUST be one of the ui_kit icon names — unknown names simply render no icon), danger (true = destructive/red styling), disabled (true = shown greyed out and not selectable), checked (true or false = a leading check-mark column, for toggles and radio groups; the PLUGIN owns that state — re-render your HTML after handling the action), separator (true = a hairline instead of an entry; leading, trailing and doubled separators are dropped), items (an array of entries = EXACTLY ONE submenu level: such an entry carries label + items INSTEAD of action, and its children must be plain entries — a nested items array is dropped, deeper menus do not exist). Limits: at most 12 entries per level, the whole attribute at most 4096 characters; entries that break a rule are dropped individually. An attribute that is not a JSON array of usable entries is ignored entirely and the tile's own menu opens instead. Elements WITHOUT the attribute always fall back to that tile menu (Hide plugin / Plugin settings…), so you only annotate what needs its own menu.", "example": "\"Photo\"" } ``` ## tooltip ```json { "attribute": "title", "usage": "Put title=\"text\" on any element. The shell REPLACES the webview's native tooltip with its own themed surface (theme tokens --sb-tooltip-bg / --sb-tooltip-text / --sb-tooltip-border / --sb-tooltip-shadow): it opens after 400 ms of pointer hover or keyboard focus, sits on the side away from the docked bar edge and is clamped to the screen; inside an open flyout — the hover preview included — it is drawn within the flyout window, above the trigger when there is room and otherwise below. If a live render changes the hovered title, the visible surface updates in place without another delay. Escape dismisses it. The shell keeps existing aria-describedby references and appends a hidden description for controls that already have a name; when an element has no text, label or ARIA name it copies the title into aria-label instead.", "example": "" } ``` ## rotator ```json { "attribute": "data-rotator", "usage": "Put data-rotator=\"up|down|left|right\" on a container; every direct child is one view, all stacked in place (the container sizes to the tallest view). The shell shows one view at a time and rolls to the next every data-rotator-interval ms (default 4000, minimum 1500) in the given direction, pausing while hovered; under prefers-reduced-motion views switch instantly. Use it to cycle tickers, rotating titles, or KPI views inside a tile.", "example": "
BTC $109,412
ETH $4,102
" } ``` ## clock ```json { "attribute": "data-clock / data-clock-text", "usage": "The shell keeps time so your plugin does not have to re-render every second — which would throw away form state, carousel position and focus. data-clock=\"\" turns an element into an animated analog clock (its hands are CSS animations, zero JS ticking). data-clock-text=\"\" fills an element with a ticking string; data-clock-format picks time (default), date, weekday, week (ISO week number), offset (UTC offset) or unix (seconds since the epoch — pair it with a data-sb-copy button). data-clock-lang sets the locale, data-clock-seconds=\"false\" hides seconds. An empty zone means the system time zone. Inside a bar tile the analog face sizes itself to the tile height, so data-clock works as an analog cover. Render ONCE and let these tick.", "example": "
Berlin ·
" } ``` ## flyoutWidth ```json { "attribute": "data-sb-flyout-width", "usage": "On the SINGLE outer HTML element of flyout or hover content, request \"wide\" (42.5rem, twice the default 21.25rem; normally 680 vs 340 CSS pixels) or a whole CSS-pixel count from 1 to 16384, without a unit. The width includes the shell padding and border and is capped to the monitor work area minus the existing side margins. Omit it for the default width. Only one request at the outer root is supported: nested requests, multiple roots, non-whitespace sibling text or invalid values fall back to the default with a deduplicated plugin-log diagnostic. Tile/popup requests are ignored and diagnosed. Re-render to change or remove the request; local tab switching alone does not change it. Hover and pinned content each use the request on the content actually displayed. Existing SDK render calls need no changes. The flyout itself never scrolls horizontally; wrap wide tables in sb-table-wrap for local horizontal scrolling. Width does not enable columns or inner scrolling: omit sb-split/sb-scroll for one outer vertical scroller, at any width. For screen-width content request 16384 and let the shell cap it to the monitor. Height stays content-sized up to the existing 78% work-area limit, then the whole content scrolls. Examples: snippets.wideFlyout, screenFlyout and splitFlyout.", "example": "

Standings

TeamPoints
North24
" } ```