Styling & Theming#

The play/pause/restart control button lives inside a Shadow DOM for style isolation — it won’t be affected by your page’s stylesheets and won’t leak styles into your content.

To let downstream projects theme the button without breaking encapsulation, all visual properties are exposed as CSS custom properties (also known as CSS variables). Because custom properties inherit through Shadow DOM boundaries, you can set them on the [data-guidestar] container or any ancestor element.

Control button custom properties#

Property

What it controls

Default

--gs-control-size

Button width and height

44px

--gs-control-radius

Border-radius

8px

--gs-control-bg

Background color

rgba(0,0,0,0.55)

--gs-control-bg-hover

Background color on hover

rgba(0,0,0,0.75)

--gs-control-border

Border shorthand

none

--gs-control-color

Icon / text color

#fff

--gs-control-icon-size

SVG icon width and height

22px

--gs-control-bottom

Bottom offset from the container

12px

--gs-control-right

Right offset from the container

12px

--gs-control-tooltip-bg

Tooltip background color

rgba(0,0,0,0.8)

--gs-control-tooltip-color

Tooltip text color

#fff

Basic example#

Set any combination of properties on the demo container:

[data-guidestar] {
    --gs-control-bg: rgba(0, 59, 77, 0.9);
    --gs-control-bg-hover: rgba(0, 125, 164, 0.9);
    --gs-control-border: 2px solid rgba(255, 255, 255, 0.2);
    --gs-control-radius: 8px;
    --gs-control-size: 44px;
}

Scoping to light / dark themes#

If your Sphinx theme supports light and dark modes (e.g. pydata-sphinx-theme), scope overrides to the appropriate data-theme attribute:

/* Dark mode (default look) */
[data-guidestar] {
    --gs-control-bg: rgba(0, 59, 77, 0.9);
    --gs-control-bg-hover: rgba(0, 125, 164, 0.9);
}

/* Light mode — more contrast */
html[data-theme="light"] [data-guidestar] {
    --gs-control-bg: rgba(0, 0, 0, 0.6);
    --gs-control-bg-hover: rgba(0, 0, 0, 0.8);
}

Repositioning the button#

The button’s position is controlled by --gs-control-bottom and --gs-control-right. To move it to a different corner:

/* Bottom-left instead of bottom-right */
[data-guidestar] {
    --gs-control-right: auto;
}

.gs-controls-host {
    right: auto;
    left: 12px;
}

Note

The .gs-controls-host element is in the light DOM (outside the Shadow DOM), so you can target it directly for positioning changes that go beyond what the custom properties support.

Making it circular#

[data-guidestar] {
    --gs-control-size: 36px;
    --gs-control-radius: 50%;
    --gs-control-icon-size: 18px;
}

Customizing the highlight#

The element highlight (orange pulse ring) is injected into the main document, not the Shadow DOM. You can override it with normal CSS:

/* Blue highlight instead of orange */
.gs-highlight {
    outline-color: rgba(0, 120, 255, 0.7);
}

/* Disable the pulse animation */
.gs-highlight {
    animation: none;
}

Customizing the caption overlay#

Captions (the semi-transparent text overlay shown when steps include a caption field) can be themed via CSS custom properties:

Property

What it controls

Default

--gs-caption-bg

Background color

rgba(0,0,0,0.72)

--gs-caption-color

Text color

#fff

--gs-caption-font-size

Font size

14px

--gs-caption-padding

Padding

10px 16px

--gs-caption-inset

Left & right inset — centres the caption and keeps it clear of the control button

68px

[data-guidestar] {
    --gs-caption-bg: rgba(0, 0, 80, 0.85);
    --gs-caption-font-size: 16px;
}

You can also apply a custom CSS class to individual captions using the captionOptions.className field in JSON step objects, or target the .gs-caption element directly:

/* Subtle border on all captions */
.gs-caption {
    border-top: 1px solid rgba(255, 255, 255, 0.15);
}

/* Change outline width and offset */
.gs-highlight {
    outline-width: 3px;
    outline-offset: 4px;
}

Full theming example#

Here is a complete example of a downstream project (like jdaviz) applying custom branding to the demo controls in its own CSS file:

/*
 * my-project-wireframe.css
 * Custom theming for the wireframe demo control button.
 */

/* Brand the control button */
[data-guidestar] {
    --gs-control-bg: rgba(0, 59, 77, 0.9);
    --gs-control-bg-hover: rgba(0, 125, 164, 0.9);
    --gs-control-border: 2px solid rgba(255, 255, 255, 0.2);
    --gs-control-radius: 8px;
    --gs-control-size: 44px;
    --gs-control-icon-size: 24px;
}

/* Adjust for light mode */
html[data-theme="light"] [data-guidestar] {
    --gs-control-bg: rgba(0, 0, 0, 0.65);
    --gs-control-bg-hover: rgba(0, 0, 0, 0.85);
    --gs-control-border: 2px solid rgba(0, 0, 0, 0.15);
}

/* Tint the highlight to match brand */
.gs-highlight {
    outline-color: rgba(0, 125, 164, 0.7);
}

Load this CSS file in your Sphinx conf.py:

html_css_files = ['my-project-wireframe.css']

Or include it via the directive’s :css: option:

.. guidestar-demo:: _static/my-wireframe.html
   :css: _static/my-project-wireframe.css
   :steps: #btn@1500:click

Wireframe CSS isolation#

When guidestar loads a wireframe HTML file, the full document (including its <style> blocks) is fetched and rendered. Browsers apply <style> elements globally — even those inserted inside a <div> — so a wireframe with rules like body { background: #111; font-size: 14px; } would ordinarily leak those styles into the surrounding documentation page.

Guidestar prevents this automatically using the CSS @scope rule (supported in Chrome 118+, Firefox 128+, Safari 17.4+). When a wireframe is loaded, the controller:

  1. Parses the HTML with DOMParser to keep styles out of the live DOM.

  2. Collects all <style> blocks from the wireframe.

  3. Rewrites body, html, and :root selectors to :scope (which, inside @scope, refers to the demo container element).

  4. Wraps the collected CSS in @scope (.gs-scope-xxxx) { } and injects it into <head> so it only applies inside that container.

  5. Sets the content div’s innerHTML to the parsed body content only, with no leftover <style> elements.

In practice, wireframe authors write normal HTML and CSS without any special precautions — the isolation is transparent.

Note

@keyframes rules are not scoped (they are inherently global and browsers hoist them out of @scope). If two wireframes on the same page define keyframes with the same name, the second definition wins. Use unique keyframe names (e.g. prefix with your project name) if this matters.

External stylesheets (<link rel="stylesheet">) in wireframes are fetched and applied globally and are not automatically scoped. If your wireframe links to an external stylesheet, wrap the relevant rules in a selector that matches only your wireframe’s root element instead of body:

/* Instead of: */
body { background: #1e1e2e; }

/* Use a root wrapper element: */
.my-app { background: #1e1e2e; }

Alternatively, keep all styles inline (<style> in the wireframe HTML) so guidestar can scope them automatically.