Skip to main content
Version: Next 🚧

Themes, fonts, and branding

Semantic themes​

Themes describe reusable intent: background, surfaces, text, accents, status colors, strokes, typography, radii, and effects.

import { createTheme, themeColor, themes } from "veena/style";

const kavriq = createTheme(themes.dark, {
name: "kavriq",
colors: {
background: "#050513",
textPrimary: "#fffaf0",
accent: "#52d8ff",
accentAlt: "#a78bfa",
},
});

class ThemedScene extends Scene {
constructor() {
super({ theme: kavriq });
}

override construct() {
this.add(Circle().fill(themeColor("accent")));
}
}

Explicit object styles win over inherited theme values. A Group can override a subtree with .theme({ colors: ... }). Resolved roles are also exposed as --veena-* CSS variables.

Fixed palette​

Use semantic roles for reusable components. Use palette when a specific swatch is intentionally part of the artwork.

import { palette } from "veena/style";

Circle().fill(palette.TEAL_C);

Fonts​

Browser/system fonts work directly:

Label("Editorial title").font("Georgia, serif").fontWeight(700);

Embed a local TTF, OTF, WOFF, or WOFF2 file:

import { fontFile } from "veena/core";

const satoshi = fontFile("Satoshi", "./assets/Satoshi-Bold.ttf", { weight: 700 });

override construct() {
this.registerFont(satoshi);
this.add(Label("KAVRIQ").font(satoshi, "Inter", "sans-serif").fontWeight(700));
}

Keep file paths literal so the bundler can discover them. Missing optional private fonts fall back cleanly. fontUrl() supports browser-loadable URLs and data URLs.

Persistent branding​

this.addBranding(Label("KAVRIQ").height(0.2).opacity(0.72), {
position: "bottomRight",
margin: 0.35,
});

Branding stays camera-independent for the full scene. Add more than one brand by calling addBranding() again with another position or an explicit at point.