Skip to content

Theme Authoring

Create custom themes to customize Sireno Deck’s appearance.

my-theme/
├── sirenodeck.json # Manifest
├── components.css # Component styles
└── assets/ # Fonts and images
├── IBM_Plex_Sans/
│ └── IBMPlexSans-Medium.ttf
└── IBM_Plex_Mono/
└── IBMPlexMono-Regular.ttf
{
"kind": "theme",
"apiVersion": 1,
"name": "my-theme",
"version": "1.0.0",
"description": "My custom theme",
"colorTokens": {
"background": "#000000",
"frame": "#333333",
"foreground": "#ffffff",
"foreground-contrast": "#ffffff",
"primary": "#3b82f6",
"accent": "#8b5cf6",
"success": "#22c55e",
"danger": "#ef4444",
"tintBlue": "#3b82f6",
"tintGreen": "#22c55e",
"tintPurple": "#8b5cf6"
},
"typography": {
"main_text": {
"fontFamily": "IBM Plex Sans",
"fontSize": 12,
"fontWeight": 500,
"letterSpacing": 0
},
"auxiliary_text": {
"fontFamily": "IBM Plex Sans",
"fontSize": 10,
"fontWeight": 400,
"letterSpacing": 0
},
"monospace": {
"fontFamily": "IBM Plex Mono",
"fontSize": 11,
"fontWeight": 400,
"letterSpacing": 0
}
},
"fonts": [
{
"fontFamily": "IBM Plex Sans",
"fontWeight": 500,
"fontStyle": "normal",
"src": "./assets/IBMPlexSans-Medium.ttf"
}
],
"assets": {
"styles": ["./components.css"]
}
}
Token CSS Variable Description
background --sireno-color-bg Main background
frame --sireno-color-frame Button border
foreground --sireno-color-fg Primary text
foreground-contrast --sireno-color-fg-contrast High-contrast text
primary --sireno-color-primary Primary accent
accent --sireno-color-accent Secondary accent
success --sireno-color-success Success state
danger --sireno-color-danger Error state
tintBlue --sireno-color-tint-blue Blue variant
tintGreen --sireno-color-tint-green Green variant
tintPurple --sireno-color-tint-purple Purple variant
{
"typography": {
"main_text": {
"fontFamily": "IBM Plex Sans",
"fontSize": 12,
"fontWeight": 500,
"letterSpacing": 0
},
"auxiliary_text": {
"fontFamily": "IBM Plex Sans",
"fontSize": 10,
"fontWeight": 400
},
"monospace": {
"fontFamily": "IBM Plex Mono",
"fontSize": 11,
"fontWeight": 400
}
}
}
.main-text {
font-family: var(--sireno-font-main_text);
font-size: var(--sireno-font-size-main_text);
font-weight: var(--sireno-font-weight-main_text);
}
{
"fonts": [
{
"fontFamily": "IBM Plex Sans",
"fontWeight": 500,
"fontStyle": "normal",
"src": "./assets/IBMPlexSans-Medium.ttf"
}
]
}
/* Button styles */
.sireno-button {
border-radius: 8px;
transition: all 0.15s ease;
}
.sireno-button:active {
transform: scale(0.95);
}
/* Custom surface */
.my-surface {
background: var(--sireno-color-bg);
border: 1px solid var(--sireno-color-frame);
}
/* Animation */
@keyframes pulse {
0%,
100% {
opacity: 1;
}
50% {
opacity: 0.5;
}
}
  • --sireno-color-* — All color tokens
  • --sireno-font-* — Typography values
  • --sireno-spacing-* — Spacing values
Terminal window
# Use theme during development
sireno run --config config.yml
# Config references theme by name, path, or npm package
theme: default
theme: ./packages/themes/my-theme
theme: @sirenodeck/theme-neon-grids

The theme: value is a single string resolved in three passes:

  1. Registered theme namedefault, light, or any theme already loaded into the registry.
  2. Path — any string starting with ./, ../, /, or a Windows drive letter resolves to a theme directory on disk.
  3. npm package — anything else is treated as a package name and resolved via Node’s resolver to <pkg>/package.json. The package directory is then loaded as a theme.

Advanced: Replace React components entirely:

{
"ui-overrides": "./components"
}

The ui-overrides path resolves to a module that exports three optional buckets. Each is shallow-merged into the default presentation; missing slots fall back to the built-in component.

components/index.ts
export const components = {
ButtonFrame: MyCustomButtonFrame,
}
export const surfaces = {
IconLabel: MyCustomIconLabel,
IconLabelProgress: MyCustomIconLabelProgress,
TemporaryError: MyCustomTemporaryError,
}
export const primitives = {
Label: MyCustomLabel,
TapIndicator: MyCustomTapIndicator,
}

Override fns receive the same props as the default component plus an optional second argument — a ThemeOverrideContext with the active theme name, colors, and typography — so they can read sibling tokens without parsing CSS variables.

Top-level helpers (deckBackground) also live in the same module:

export const deckBackground = ({ className }) =>
`${className} bg-[radial-gradient(...)]`