The components are designed to be styled from the host page using CSS custom properties. There are no Sass overrides or build steps — set a variable in :root (or any ancestor) and the value flows through Shadow DOM to every component on the page.
Every component lives in a Shadow DOM, but CSS custom properties pierce that boundary. Set a token at the root of your page and the components see it:
:root {
--accent: #ff6f00; /* changes link / focus / hover accent everywhere */
--medium-font: 18px; /* shrinks the base font size globally */
}
You can also scope an override to a region of the page (e.g. only the sidebar) by setting the variable on a specific ancestor:
aside.sidebar {
--menu-active-bg: #fff5e6; /* only the menu inside this aside picks up the override */
}
Where a component-specific knob exists (--menu-*, --query-*, --include-*), it takes precedence over the global token it's based on, so you can retheme one component without touching others.
Light is the default. Add data-theme="dark" to the <html> element (or any ancestor of your components) to switch to the bundled dark palette:
<html data-theme="dark">
...
</html>
The dark theme reassigns these tokens — every component that respects them flips automatically:
| Token | Light default | Dark override |
|---|---|---|
| --bg | #f5f5f5 | #1a1a1a |
| --surface | #ffffff | #252525 |
| --border | #d0d0d0 | #3e3e3e |
| --text | #000000 | #e0e0e0 |
| --text-muted | #4d4d4d | #909090 |
| --hover | #eaf2fb | #2e2e2e |
| --focus-bg | #ebf5fb | #2a2e34 |
| --code-bg | #f4f4f4 | #1e1e1e |
| --accent | #1F618D | #4dabf7 |
| --accent-dark | #2980b9 | #339af0 |
To follow the system preference automatically:
<script>
if (matchMedia('(prefers-color-scheme: dark)').matches) {
document.documentElement.dataset.theme = 'dark';
}
</script>
To define your own theme, override any subset of tokens — you don't have to redefine the whole palette:
:root[data-theme="brand"] {
--accent: #ff6f00;
--accent-dark: #c44a00;
--hover: #fff3e0;
--focus-bg: #ffe0b2;
}
| Variable | Default (light) | What it controls |
|---|---|---|
| --bg | #f5f5f5 | Page background (intended for the host page; components mostly use --surface) |
| --surface | #ffffff | Component surface fill — popups, modals, cards, table cells |
| --border | #d0d0d0 | Lines and dividers |
| --text | #000000 | Primary text color |
| --text-muted | #4d4d4d | Secondary text — captions, field labels |
| --accent | #1F618D | Brand color — links, active states, focus rings |
| --accent-dark | #2980b9 | Hovered/pressed accent |
| --hover | #eaf2fb | Background tint on hover |
| --focus-bg | #ebf5fb | Background tint on the focused/active item |
| --code-bg | #f4f4f4 | Background for <code> and <pre> |
| --error | #e74c3c | Error message color and outline |
| --success | #27ae60 | Success state (reserved — currently unused) |
Three sizes (small/medium/large) plus a separate --font-size alias for "the default body size." Medium is the default. Nothing in any component renders below 16 px.
| Variable | Default | What it controls |
|---|---|---|
| --small-font | 16px | Compact text — field labels, sort markers, button text in tight contexts, badges |
| --medium-font | 20px | Default body / control / table size |
| --large-font | 24px | Section headers (e.g. sol-query subject banner) |
| --font-size | var(--medium-font, 20px) | Alias the components reach for first; override to retag the default size |
| --font-ui | 'Segoe UI', Roboto, sans-serif | UI font family |
| --font-mono | 'Cascadia Code', 'Fira Code', 'Consolas', monospace | Monospace family for code blocks and Turtle output |
| --font-weight-normal | 400 | Body weight |
| --font-weight-bold | 600 | Bold weight (table headers, active items, field labels) |
| --line-height-tight | 1.25 | Headings |
| --line-height-base | 1.5 | Body text |
--medium-font (or --font-size). Components that explicitly use --small-font or --large-font for emphasis stay at their tier — small stays smaller than medium even after you change medium.
| Variable | Default | Used for |
|---|---|---|
| --space-xs | 2px | Tight gaps (popup item gap, nav item gap) |
| --space-sm | 4px | Small gaps (small text margin) |
| --space-md | 8px | Default padding for nav items, table cells |
| --space-lg | 12px | Larger paddings (subject-nav, error/loading) |
| --space-xl | 16px | Content paddings, single-value cells |
| --space-2xl | 24px | Modal padding, section breaks |
| --radius-sm | 4px | Buttons, error boxes, links |
| --radius-md | 6px | Popups, modals |
| Variable | Default | Where |
|---|---|---|
| --shadow | rgba(0,0,0,0.08) | Generic surface drop shadow (cards) |
| --shadow-popup | 0 4px 12px rgba(0,0,0,0.12) | Submenu popup elevation |
| --shadow-modal | 0 8px 24px rgba(0,0,0,0.25) | Modal dialog elevation |
| --scrim | rgba(0,0,0,0.4) | Backdrop dim behind modals |
Override these to retheme just the menu without touching anything else.
| Variable | Default | What it controls |
|---|---|---|
| --menu-nav-min-width | 140px | Sidebar nav minimum width |
| --menu-nav-max-width | 260px | Sidebar nav maximum width |
| --menu-popup-min-width | 160px | Submenu popup minimum width |
| --menu-button-padding | var(--space-md) var(--space-lg) | Menu item padding (shorthand: vertical horizontal) |
| --menu-button-radius | var(--radius-sm, 4px) | Menu item corner radius |
| --menu-hover-bg | var(--hover, #f0f0f0) | Background on hover |
| --menu-hover-color | var(--accent-dark, #1976d2) | Text color on hover |
| --menu-active-bg | var(--focus-bg, #e3f2fd) | Background of the current item |
| --menu-active-color | var(--accent-dark, #1976d2) | Text color of the current item |
| Variable | Default | What it controls |
|---|---|---|
| --query-th-bg | var(--accent-dark, #2C3E51) | Table header background |
| --query-th-color | #fff | Table header text |
| --query-row-alt-bg | var(--hover, #fafafa) | Zebra-stripe color for even rows |
| --query-cell-padding | var(--space-md) var(--space-lg) | Cell padding (vertical horizontal) |
| --query-link-color | var(--accent, #0066cc) | Cell link color |
| --query-bnode-link-color | var(--text-muted, #777) | Color of "[…]" blank-node placeholder links |
| --bnode-modal-max-width | min(90vw, 700px) | Width cap for the blank-node properties modal |
| --bnode-modal-max-height | 80vh | Height cap for the blank-node properties modal |
| Variable | Default | What it controls |
|---|---|---|
| --include-code-bg | var(--code-bg, #f0f0f0) | Background of inline <code> and <pre> blocks |
| --include-code-padding | .1em .3em | Padding around inline code |
| --include-pre-padding | var(--space-lg, 12px) | Padding inside <pre> blocks |
| --include-blockquote-border | var(--border, #ccc) | Left border color of blockquotes |
| --include-blockquote-color | var(--text-muted, #555) | Blockquote text color |
For things that can't be themed via variables alone, components expose part="…" hooks (style from the host page with ::part(name)) and <slot>s (insert your own HTML into a known position, then style it with ::slotted()). The deliberate stance is to keep this surface small — exposing a part or slot is a public-API commitment that's slower to evolve than CSS variables.
| Component | Part | What it is |
|---|---|---|
| sol-modal | trigger | The button that opens the modal. Default styling is the standard .sol-btn. |
sol-modal::part(trigger) {
background: #ff6f00;
color: #fff;
border-radius: 999px;
padding: 8px 16px;
}
| Component | Slot | Fallback | What it is |
|---|---|---|---|
| sol-modal | (default) | literal text Open |
Inside the trigger button. Put your own label or icon between <sol-modal> tags to override the default text. |
<sol-modal title="Edit settings">
<span>⚙ Settings</span> <!-- replaces the "Open" fallback -->
</sol-modal>
<style>
/* ::slotted() reaches the host-page node we passed in */
sol-modal::part(trigger) ::slotted(span) { letter-spacing: .05em; }
</style>
Most surfaces ship as CSS variables rather than parts. Quick reference for the things authors most often want to retheme:
| To restyle… | Reach for… |
|---|---|
| Brand color anywhere | --accent + --accent-dark |
| The modal's open button | sol-modal::part(trigger) |
| The modal's open-button label | Default <slot> in <sol-modal> |
| The modal's body, close button, scrim | --surface, --shadow-modal, --scrim (no parts yet) |
| sol-menu sidebar / popup | --menu-* variables (see sol-menu knobs) |
| sol-query table headers / cells | --query-* variables (see sol-query knobs) |
| sol-query view container | Style the host element directly: sol-query { … } — nothing inside the shadow root reaches it |
| sol-include rendered content | --include-* variables; nested HTML appears in the host's light DOM, so normal CSS selectors apply. |
| sol-login button text / icon | No part yet — host-page CSS targeting sol-login can adjust its outer box; for inner button restyle, override --accent/--surface. |
| Loading / error states across components | --text-muted (loading), --error (errors) |
| Per-region override (only one section) | Set the variable on an ancestor element, not :root — see How customization works. |
:root {
--accent: #ff6f00;
--accent-dark: #c44a00;
}
:root {
--medium-font: 16px;
--small-font: 14px; /* OK to drop below 16px if you accept the WCAG tradeoff */
--space-md: 6px;
--space-lg: 8px;
}
:root {
--menu-active-bg: #fff3e0;
--menu-active-color: #c44a00;
--menu-hover-bg: #fff8e1;
}
:root {
--query-row-alt-bg: var(--surface); /* makes even rows match odd rows */
}
<script>
const root = document.documentElement;
const apply = e => root.dataset.theme = e.matches ? 'dark' : 'light';
const mql = matchMedia('(prefers-color-scheme: dark)');
apply(mql); mql.addEventListener('change', apply);
</script>
:root[data-theme="ocean"] { --accent: #0277bd; --accent-dark: #01579b; --hover: #e1f5fe; }
:root[data-theme="forest"] { --accent: #2e7d32; --accent-dark: #1b5e20; --hover: #e8f5e9; }
:root[data-theme="sunset"] { --accent: #e64a19; --accent-dark: #bf360c; --hover: #fff3e0; }
// JS to switch
document.documentElement.dataset.theme = 'forest';