Theming and Styling
Style Triiiceratops with vanilla-CSS design tokens: three approaches, from swapping a built-in theme to overriding individual tokens.
Triiiceratops ships a small set of vanilla-CSS design tokens. There are three ways to style the viewer, from easiest to most granular:
Pick a built-in theme (
themeprop).Override tokens with the
themeConfigprop (typed, friendly names).Set the underlying CSS variables directly on the host element.
You don't have to use them in order. You can override just one piece of a built-in theme (e.g. tweak primary via themeConfig while keeping dark's other tokens as-is), or skip built-in themes entirely and build a fully custom theme from scratch by setting the CSS variables yourself.
These three are the whole styling surface. The viewer's shadow-DOM internals are not reachable from the outside: there is no ::part() surface, no way to inject a consumer stylesheet into the shadow root, and no light-DOM styling hook for internal elements. You still control the host element as usual: its layout, size, borders, and any other CSS on it.
Every host takes the same two inputs, theme and themeConfig: as props in React, Vue, and Svelte, and as an attribute plus a property on the custom element. Pick your stack's tab in the examples below.
The three methods compose, but they do not have equal precedence. From lowest to highest: the default (light) tokens, CSS variables inherited from the host/page, an explicit built-in theme, then themeConfig inline styles. If you set theme, that selected theme wins over CSS variables set outside the viewer; use themeConfig for overrides that must win over an explicit built-in theme.
1. Built-in themes
Four themes ship with the viewer: two light (light, teal) and two dark (dark, dracula). Theme names are case-sensitive. If no theme is set, the viewer uses light. The viewer does not detect the user's color-scheme preference. To follow it, set the theme yourself: theme={prefersDark ? 'dark' : 'light'}. A viewer can also inherit CSS variables from the host page.
<triiiceratops-viewer manifest-id="..." theme="dark"></triiiceratops-viewer>import { TriiiceratopsViewer } from 'triiiceratops/react';
export function Reader() {
return <TriiiceratopsViewer manifestId="..." theme="dracula" />;
}<template>
<TriiiceratopsViewer manifest-id="..." theme="dracula" />
</template><script>
import { TriiiceratopsViewer } from 'triiiceratops/svelte';
// Import the library styles once in your app:
import 'triiiceratops/style.css';
</script>
<TriiiceratopsViewer manifestId="..." theme="dracula" />2. Custom theme configuration
Override individual tokens with themeConfig. No CSS is required. Colors accept Hex (#3b82f6), RGB (rgb(59, 130, 246)), or OKLCH (oklch(60% 0.25 250)); the viewer applies the value as written.
Palette
Keyword | Description | CSS Variable |
|---|---|---|
| Primary brand color (buttons, active states) |
|
| Text color on a primary background |
|
| Neutral color (tooltips, active menu items) |
|
| Text color on a neutral background |
|
| Success state |
|
| Text color on a success background |
|
| Warning state |
|
| Text color on a warning background |
|
| Error state |
|
| Text color on an error background |
|
Surfaces
Surface tokens are named by the region they color. panelBg is the default for every panel (including plugin panels); galleryBg and inputBg follow viewerBg by default.
Keyword | Description | CSS Variable |
|---|---|---|
| Main viewer/canvas background |
|
| Toolbar + canvas-nav controls background |
|
| Default background for all side panels |
|
| Thumbnail gallery background |
|
| Form input/control surface |
|
| Borders and dividers |
|
Content (foreground)
content is the global text/icon color. The per-region tokens below inherit from it and let you change the text color in one region without affecting the rest.
Keyword | Description | CSS Variable |
|---|---|---|
| Global default text/icon color |
|
| Text color inside panels |
|
| Text color inside the toolbar |
|
| Text color over the viewer surface |
|
| Text color inside the gallery |
|
Per-panel overrides
Each panel's background and text default to panelBg / panelContent. Override one panel without affecting the others. Setting panelBg: 'green' tints every panel; additionally setting metadataPanelBg: 'white' overrides just the metadata panel.
Keyword ( | Panel | CSS Variable ( |
|---|---|---|
| Information / metadata panel |
|
| Annotations panel |
|
| Search panel |
|
| Table-of-contents panel |
|
| Collection panel |
|
Plugin panels have no per-panel token of their own: they take panelBg / panelContent like every other panel. First-party plugin CSS reads the public --tri-* tokens above (--tri-input-bg, --tri-surface-border, --tri-radius-panels, and the palette), so changing those also changes the plugin panels. A plugin that defines its own custom property can be set through the cssVars key.
Border radius
The three top-level tokens set the defaults; the per-region overrides inherit from them, so you can keep everything consistent or adjust one region.
Keyword | Description | Inherits | CSS Variable |
|---|---|---|---|
| Large containers (cards, panels, popovers) | — |
|
| Buttons, inputs, and button groups | — |
|
| Small selectors (checkboxes, badges) | — |
|
| Toolbar corners |
|
|
| Panel corners |
|
|
| Canvas-nav controls pill |
|
|
| The buttons inside the canvas-nav pill |
|
|
Fully rounded controls: The canvas-nav controls inherit the button radius by default. For a capsule with circular buttons, set
radiusControls: '9999px'andradiusControlsButtons: '9999px'.
Sizing & effects
Keyword | Description | CSS Variable | Example |
|---|---|---|---|
| Base padding/size for inputs |
|
|
| Base padding/size for selectors |
|
|
| Border width |
|
|
| Drop shadows ( |
|
|
| Browser UI hint ( |
|
|
Complete public token reference
Every token below is part of the semver-governed public customization surface. Variables outside the --tri-* namespace are internal implementation details with no stability guarantee. These include --ui-* layout plumbing and component-local --btn-* / --range-* variables. The exceptions are the two typeface properties documented in Typefaces below. Set a token either by its themeConfig key or by writing the raw CSS variable; tokens marked — (raw only) have no typed key and must be set through cssVars or plain CSS. The table lists core’s own tokens. A plugin may declare further --tri-* properties of its own, governed by that plugin’s version and documented on its page: the annotation editor’s drawing layer is the first-party example.
Palette
| CSS variable | themeConfig key |
|---|---|
--tri-color-primary | primary |
--tri-color-primary-content | primaryContent |
--tri-color-primary-text | — (raw only) |
--tri-color-neutral | neutral |
--tri-color-neutral-content | neutralContent |
--tri-color-success | success |
--tri-color-success-content | successContent |
--tri-color-warning | warning |
--tri-color-warning-content | warningContent |
--tri-color-error | error |
--tri-color-error-content | errorContent |
Surfaces
| CSS variable | themeConfig key |
|---|---|
--tri-viewer-bg | viewerBg |
--tri-toolbar-bg | toolbarBg |
--tri-panel-bg | panelBg |
--tri-gallery-bg | galleryBg |
--tri-input-bg | inputBg |
--tri-surface-border | surfaceBorder |
Content / foreground
| CSS variable | themeConfig key |
|---|---|
--tri-content | content |
--tri-panel-content | panelContent |
--tri-toolbar-content | toolbarContent |
--tri-viewer-content | viewerContent |
--tri-gallery-content | galleryContent |
Per-panel overrides
| CSS variable | themeConfig key |
|---|---|
--tri-metadata-panel-bg | metadataPanelBg |
--tri-metadata-panel-content | metadataPanelContent |
--tri-annotations-panel-bg | annotationsPanelBg |
--tri-annotations-panel-content | annotationsPanelContent |
--tri-search-panel-bg | searchPanelBg |
--tri-search-panel-content | searchPanelContent |
--tri-structures-panel-bg | structuresPanelBg |
--tri-structures-panel-content | structuresPanelContent |
--tri-collection-panel-bg | collectionPanelBg |
--tri-collection-panel-content | collectionPanelContent |
Border radius
| CSS variable | themeConfig key |
|---|---|
--tri-radius-selector | radiusSelector |
--tri-radius-buttons | radiusButtons |
--tri-radius-box | radiusBox |
--tri-radius-toolbar | radiusToolbar |
--tri-radius-panels | radiusPanels |
--tri-radius-controls | radiusControls |
--tri-radius-controls-buttons | radiusControlsButtons |
Sizing
| CSS variable | themeConfig key |
|---|---|
--tri-size-selector | sizeSelector |
--tri-size-field | sizeField |
Border / effects
| CSS variable | themeConfig key |
|---|---|
--tri-border | border |
--tri-depth | depth |
Typefaces
The viewer ships no font files and downloads none. It reads two custom properties and falls back to system fonts, so by default it uses the platform's UI sans-serif and monospace fonts. These two are public API alongside the --tri-* tokens above, despite being outside that namespace: they are the only way to match the viewer's fonts to your site.
CSS variable | Applies to | Fallback when unset |
|---|---|---|
| all text in the viewer |
|
|
|
|
Neither has a themeConfig key: fonts belong to the host page rather than to a theme, and the viewer is meant to inherit them. Set them wherever they will reach the viewer: on :root for a whole page, or on the viewer's own element to set fonts for that viewer only:
:root {
--default-font-family: 'Source Serif 4', Georgia, serif;
--default-mono-font-family: 'Source Code Pro', ui-monospace, monospace;
}Both take a full font stack rather than a single family. Include fallbacks: the viewer does not load fonts, so a stack that names only a web font your page has not loaded renders in the browser's default font. Loading the font (an @font-face rule, a <link>, or whatever your site already does) is up to you.
Example usage
As an attribute, JSON-encoded:
<triiiceratops-viewer
manifest-id="..."
theme="light"
theme-config='{"primary":"#ff0000","panelBg":"#fafafa","radiusBox":"0px"}'
></triiiceratops-viewer>Or as a property, from JavaScript:
<triiiceratops-viewer manifest-id="..."></triiiceratops-viewer>
<script>
const viewer = document.querySelector('triiiceratops-viewer');
viewer.theme = 'light';
viewer.themeConfig = {
primary: '#3b82f6',
toolbarBg: '#1f2937',
panelBg: '#f3f4f6',
metadataPanelBg: '#ffffff',
content: '#1f2937',
radiusBox: '0.75rem',
border: '2px',
};
</script>Assign a new themeConfig object when updating from JavaScript. Mutating a nested property on the existing object does not notify the custom element.
import { TriiiceratopsViewer } from 'triiiceratops/react';
import type { ThemeConfig } from 'triiiceratops';
// Defined outside the component (or memoized) so the wrapper's shallow
// equality check sees a stable value and never re-applies it.
const customTheme: ThemeConfig = {
primary: '#0ea5e9',
panelBg: '#0f172a',
radiusBox: '1rem',
};
export function Reader() {
return (
<TriiiceratopsViewer
manifestId="..."
theme="light"
themeConfig={customTheme}
/>
);
}<script setup lang="ts">
import { shallowRef } from 'vue';
import { TriiiceratopsViewer } from 'triiiceratops/vue';
import type { ThemeConfig } from 'triiiceratops';
const customTheme = shallowRef<ThemeConfig>({
primary: '#0ea5e9',
panelBg: '#0f172a',
radiusBox: '1rem',
});
</script>
<template>
<TriiiceratopsViewer
manifest-id="..."
theme="light"
:theme-config="customTheme"
/>
</template><script lang="ts">
import { TriiiceratopsViewer } from 'triiiceratops/svelte';
import 'triiiceratops/style.css';
import type { ThemeConfig } from 'triiiceratops';
const customTheme: ThemeConfig = {
primary: '#0ea5e9',
panelBg: '#0f172a',
radiusBox: '1rem',
};
</script>
<TriiiceratopsViewer manifestId="..." theme="light" themeConfig={customTheme} />Raw CSS variables
For tokens without a typed key, such as --tri-color-primary-text or a custom property a plugin reads, use the cssVars key. Keys are CSS variable names without the leading --, and the viewer does not check them against its own token list, so a name nothing reads has no effect:
viewer.themeConfig = {
panelBg: 'oklch(20% 0.02 277)',
cssVars: {
'tri-color-primary-text': '#c7d2fe',
'my-plugin-accent': '#0b1020',
},
};3. Styling with CSS variables
Because the tokens are plain custom properties that inherit through the shadow boundary, you can theme the viewer from your own CSS by targeting the host element. This is equivalent to themeConfig only when no explicit theme is set; if a built-in theme is selected, the theme's token values win over host CSS variables.
triiiceratops-viewer {
--tri-color-primary: oklch(65% 0.25 260);
--tri-panel-bg: #1e1e2e;
--tri-metadata-panel-bg: #181825; /* override one panel */
--tri-radius-box: 0;
--tri-radius-controls: 9999px; /* keep the controls pill rounded */
}A selected built-in theme wins over CSS variables you set this way; themeConfig (inline styles) wins over everything. In Svelte/light-DOM usage, you can also switch among built-in themes by setting data-theme on an ancestor when you leave the viewer's theme prop unset:
<div data-theme="dracula">
<TriiiceratopsViewer manifestId="..." />
</div>For the web component, prefer the theme attribute/property for built-in themes:
<triiiceratops-viewer manifest-id="..." theme="dracula"></triiiceratops-viewer>