How do I reskin the dashboards for my brand?¶
Customization walkthrough — Developer / Product Owner. Reskinning + extending.
The story¶
You've stood up the dashboards against your data. Marketing walks by, sees the demo's "Sasquatch National Bank" forest-green palette in production, and asks the obvious question: can we get our actual brand colors on this?
The answer is yes, and it takes about ten minutes. The visual
layer never references hex colors directly — every accent,
foreground, background, and link tint resolves at generate time
from the L2 institution YAML's inline theme: block. To
rebrand, you add a theme: block to your L2 YAML, regenerate,
deploy. The analysis name, KPI accent colors, table-cell tints,
and conditional formatting all flip together.
The rebrand surface is deliberately small: one block on one YAML. The same constraint that keeps the visual layer rebrand-friendly (no hardcoded hex codes in app code) is what makes this walkthrough this short.
The question¶
"My bank's brand book says navy blue and silver, our typography is Inter, and our analysis names need to read 'SNB Treasury' not 'L1 Reconciliation'. Where do I drop those in?"
Where to look¶
Two reference points:
- Your L2 institution YAML's
theme:block — the per-instance brand declaration. Seetests/l2/spec_example.yaml(default generic palette) andtests/l2/sasquatch_pr.yaml(full forest green for the demo persona) for examples. src/quicksight_gen/common/l2/theme.py— theThemePresetdataclass that validates the YAML block at load time. The field list there is the contract for the YAML'stheme:block.
There is no longer a global preset registry to extend. Each L2
institution carries its own theme. The CLI no longer accepts a
--theme-preset flag.
What you'll see in the demo¶
Look at any L2 YAML's theme: block to see the color token
contract. tests/l2/spec_example.yaml carries the canonical
generic palette:
theme:
theme_name: "QuickSight Gen Theme"
version_description: "Auto-generated dashboard theme"
analysis_name_prefix: null
data_colors:
- "#2E5090" # primary brand
- "#E07B39" # accent series 2
- "#3A9E6F" # accent series 3
# ... 5 more bulk-fill colors
empty_fill_color: "#E5E7EB"
gradient: ["#D6E4F5", "#2E5090"]
primary_bg: "#FFFFFF"
secondary_bg: "#F8F9FA"
primary_fg: "#1F2933"
accent: "#2E5090"
accent_fg: "#FFFFFF"
link_tint: "#E8EFF9"
danger: "#C62828"
warning: "#E65100"
success: "#2E7D32"
# ... rest of the tokens (foreground variants, dimension/measure)
Every token has a job. The three load-bearing ones (the ones that change the most when you swap in your brand):
accent— the dashboard's primary brand color. Drives KPI value text, clickable cell text, accent-colored bar fills, filter pill backgrounds. The single token most strongly tied to "this looks like our brand."primary_fg— body text color. Almost always a near-black on a near-white background. Pick a true neutral; an off-spec primary_fg can make the whole dashboard feel "off" without the user being able to name why.link_tint— the pale-accent background applied to table cells whose click target is a right-click menu (drill-action cells). Should be ~12% opacity ofaccenton white. The#E8EFF9in the spec_example default is exactly that for#2E5090.
The rest (data palette, gradient, semantic colors, dimension / measure colors) are bulk fills — they affect chart series order and KPI tile chrome, but the brand-recognition load lives on the three above.
What it means¶
Reskinning is two steps:
Step 1 — Declare your theme block on your L2 YAML¶
Add (or edit) the theme: block on your L2 institution YAML.
Pattern, using a hypothetical ACME Treasury palette:
# acme_treasury.yaml — your institution YAML
instance: acme_treasury
description: |
ACME Treasury — internal cash concentration + customer DDA reconciliation.
# ... accounts / account_templates / rails / etc.
theme:
theme_name: "ACME Treasury Theme"
version_description: "ACME Treasury brand palette"
analysis_name_prefix: null # production: no prefix
data_colors:
- "#0A2647" # _SNB_NAVY — primary brand
- "#D4A017" # _SNB_GOLD — accent series 2
- "#A4B0BE" # _SNB_SILVER — accent series 3
- "#5E8B7E"
- "#B85C38"
- "#6B4C8A"
- "#3A9E6F"
- "#7A7A72"
empty_fill_color: "#D9D9D9"
gradient: ["#D6E4F5", "#0A2647"]
primary_bg: "#FFFFFF"
secondary_bg: "#FBF7EE" # parchment
primary_fg: "#1F2933" # ink
secondary_fg: "#A4B0BE"
accent: "#0A2647"
accent_fg: "#FFFFFF"
link_tint: "#E5EAF2" # ~12% navy on white
danger: "#C62828"
danger_fg: "#FFFFFF"
warning: "#E65100"
warning_fg: "#FFFFFF"
success: "#2E7D32"
success_fg: "#FFFFFF"
dimension: "#0A2647"
dimension_fg: "#FFFFFF"
measure: "#1F2933"
measure_fg: "#FFFFFF"
If you omit the theme: block entirely, build_theme returns
None and the deploy skips emitting a custom Theme resource —
AWS QuickSight CLASSIC takes over for that institution
(silent-fallback contract). Useful for quick smoke tests where
brand colors don't matter yet.
Step 2 — Regenerate and deploy¶
quicksight-gen json apply --l2 acme_treasury.yaml -c config.yaml -o out/
quicksight-gen json apply -c config.yaml -o out/ --execute
The deploy delete-then-creates the theme + analyses + dashboards with your new tokens. Existing user-saved bookmarks survive (the dashboard ID is stable across re-deploys); the visual chrome flips on next load.
Drilling in¶
A few tokens to know about beyond the obvious accent:
analysis_name_prefix— set tonullfor production (analysis reads "L1 Reconciliation Dashboard"). Set to"Demo"to name analyses "Demo — L1 Reconciliation Dashboard" and visually distinguish demo vs production analyses in the QuickSight authoring UI.data_colors— the eight-color series palette. First three are most prominent (single-series KPIs, two-series bar charts, three-segment stacked charts). Pick three brand colors; the remaining five are bulk fills used only on high-cardinality breakdowns.gradient—[light, dark]for any min/max gradient fills (currently the aging bar chart shaded by bucket). Pick a tinted version ofaccentfordarkand a near-white tinted version forlight. Don't pick a complementary color; the visual coherence depends on the gradient reading as "more of the same brand color."link_tint— keep this paler than you think. The pattern guideline (CLAUDE.md "Conventions") is that accent text on a pale-tint background signals a right-click drill action. Customers who pick a saturated link_tint accidentally make every drill cell look like a primary CTA — the cell becomes hard to read and competes with the accent KPIs. ~12% opacity of accent on white is the safe target.
Next step¶
Once your theme block is declared and the deploy reflects your brand:
- Spot-check the three load-bearing surfaces. Open the L1
Today's Exceptions sheet and check: KPI text colors
(
accent), table cells with right-click drills (link_tintbackground), and the aging bar chart (gradient + data_colors series order). These are the three places where a wrong token surfaces most visibly. - Confirm the analysis name. With
analysis_name_prefix: null, the analysis in QuickSight reads its app's display name (no prefix). Demo themes often use"Demo"to flag demo deploys. - Multi-instance note. Each L2 YAML carries its own
theme:. Deploying L1 againstsasquatch_pr.yamlandacme_treasury.yamlsimultaneously gives you two dashboards with two distinct themes — no per-app preset juggling required.
Brand assets on the docs site¶
The docs site (the mkdocs handbook published from docs export)
also reads brand assets from the L2 YAML's theme: block. Two
optional fields:
theme:
# ... colors above ...
logo: "https://example.com/your-logo.svg"
favicon: "https://example.com/your-favicon.ico"
Both fields accept either:
- A URL (
http://,https://, or protocol-relative//) — passed through verbatim to mkdocstheme.logo/theme.favicon. - An absolute file path (must start with
/) — copied into the docs build at render time as<docs_dir>/img/_l2_logo<ext>and<docs_dir>/img/_l2_favicon<ext>, with the theme keys rewritten to the docs-relative path.
Relative paths are rejected (their resolution would depend on
the integrator's working directory at build time). When either
field is omitted or null, the docs site falls back to whatever
mkdocs.yml ships with — for the canonical site, that's the SNB
mark.
Related walkthroughs¶
- How do I configure the deploy for my AWS account? —
the
config.yamldeploy contract; theme declarations now live on the L2 YAML, not in this file. - How do I publish docs against my L2? — the end-to-end docs export + render flow.