Skip to content

Themes & Styling

Control the visual appearance of your dashboard globally or per-chart.

For the implementation-backed inventory of single-chart properties, see the Single-Chart Property Catalog. This page explains how that chart property surface relates to reusable themes and style presets.

For the current design-direction note behind Dataface's neutral frame colors, including why the product is exploring near-black ink and an off-white app canvas, see Tonal Foundations.

For the DFT-specific chart palette direction, including the new default categorical and hero palettes with visible swatch previews, see DFT Color Palettes.


Themes

Dataface includes 18+ built-in themes that apply consistent styling across your entire dashboard. Built-ins live in dataface/core/defaults/chart_themes/, one file per theme, while your project-specific additions still go in dataface.yml under themes:.

Internally, Dataface separates:

  • Theme = CSS-like painting of the chart scaffold
  • Style Preset = HTML-like chart scaffold
  • Chart = per-chart authored meaning, field bindings, and one-off framing

That means fonts, colors, surfaces, palette choices, stroke/fill, widths, and sizes belong in themes, while defaults like axis side, grid/domain presence, legend side, title anchoring, and other scaffold decisions belong in style presets. Field bindings and chart-specific meaning stay chart-authored.

The runtime merge order is: base vega.config -> selected style_preset -> theme-paired preset overlay (when configured) -> selected theme.

Ownership Model

The chart property catalog uses a four-bucket ownership model:

  • Chart: chart-authored by default
  • Style Preset: reusable scaffold/default-layout behavior
  • Theme: reusable visual styling default
  • None: intentionally kept out of reusable preset/theme ownership

This means the catalog is the full property inventory, while themes and style presets are assignment layers over that inventory.

Examples:

  • x, y, color, theta, filters, and href are chart-owned
  • axis label angle, legend placement, grid/domain presence, and header wrapping are preset-owned defaults
  • background fills, palettes, mark color/stroke styling, and spark/bar paint surfaces are theme-owned defaults
  • compatibility surfaces such as series should stay out of both

Using a Theme

Add theme: at the top level of your dashboard:

title: "Sales Dashboard"
theme: dark

queries:
  sales: SELECT * FROM sales

layout:
  - chart: revenue_chart

Available Themes

Theme Description Background
light Clean default theme White
dark Dark mode with blue-gray tones Dark blue-gray
minimal Clean with no gridlines or borders White
excel Microsoft Excel style White
ggplot2 R's ggplot2 style Light gray
quartz Quartz design style Off-white
vox Vox Media design White
fivethirtyeight FiveThirtyEight style White
latimes LA Times newspaper style White
urbaninstitute Urban Institute style White
googlecharts Google Charts style White
powerbi Microsoft Power BI style White
carbonwhite IBM Carbon Design (light) White
carbong10 IBM Carbon (slightly darker) #f4f4f4
carbong90 IBM Carbon (dark) #262626
carbong100 IBM Carbon (darkest) #161616
vega-lite-explorer Exploratory preset for surveying Vega-Lite theming surface Warm ivory
vega-lite-explorer-rich Reference-oriented preset with broader Vega-Lite default coverage Warm ivory

Theme Examples

Dark Theme:

title: "Analytics Dashboard"
theme: dark

layout:
  - chart: daily_metrics
  - chart: revenue_breakdown

FiveThirtyEight Theme:

title: "Election Results"
theme: fivethirtyeight

layout:
  - chart: poll_results

What Themes Control

Themes automatically style:

  • Dashboard background - Overall page background color
  • Chart colors - Bars, lines, areas, points
  • Axis styling - Grid lines, labels, titles
  • Tables - Header backgrounds, borders, stripes
  • Variables - Input backgrounds, borders, labels
  • Titles - Text color and contrast

In catalog terms, themes are the default owner for properties such as:

  • style.background
  • style.color_scheme
  • visual mark.* leaves such as color, fill, stroke, opacity, and stroke width
  • spark and spark_bar paint surfaces such as style.bar_color, style.bar_background, SparkConfig.color, and SparkConfig.background

Themes should not be the primary home for chart scaffolding defaults. If you find yourself deciding where an axis sits or whether grids/domains are shown by default, that belongs in a style preset instead.

More precisely:

  • theme answers how the scaffold is painted
  • style preset answers what scaffold exists and where it sits
  • chart answers what this specific chart means and how it binds data

Title overflow is a style-owned scaffold behavior. Use style.title.overflow to choose between clip, truncate, wrap-two, and wrap. The default is wrap-two. clip hard-cuts text, truncate adds an ellipsis, wrap-two wraps to two lines and ellipsizes the second line when needed, and wrap fully wraps. The same setting applies to Vega-Lite, table, and KPI chart titles.

For tables, header labels inherit the chart title overflow mode by default. You can override that with style.header_overflow for the whole table or style.columns[].header_overflow for a specific column.

Style Presets

Style presets are reusable chart scaffolding presets. Built-ins live in dataface/core/defaults/style_presets/ and are assembled into the runtime config under style_presets:.

Dataface ships with a small set of canonical presets that can inherit from one another so we do not duplicate the same scaffolding rules across many themes.

Text-to-Mark Spectrum

When choosing or naming a style preset, it can help to place the display on a discrete text-to-mark spectrum:

table -> colored table -> spark table -> graphic table -> chart -> mini-chart

Each step shifts the balance away from textual and numeric cells and toward graphical marks as the primary carrier of statistical meaning.

  • table: a statistical display in which values are carried primarily by numbers, words, labels, and tabular layout. It may use meaningful graphical devices such as spacing, rules, line weight, or line style to organize and express relationships, but statistical marks do not yet dominate the display.
  • colored table: a statistical table in which color carries some statistical meaning. Examples include negative values shown in red or cells shaded by magnitude.
  • spark table: a table that includes tiny embedded statistical marks within cells or columns. Examples include sparklines, small bars, or other compact in-cell indicators.
  • graphic table: a hybrid form in which tabular text and graphical marks share the work more evenly, while the row-and-column layout remains clearly visible.
  • chart: a more conventionally chart-like statistical display in which most of the canvas is devoted to marks, while text mainly serves axes, labels, legends, or annotation.
  • mini-chart: a highly compact statistical chart with very little or no text, where meaning is carried mostly by marks such as shape, position, length, angle, area, or color.

The spectrum does not depend on a strict boundary between tables and charts. Instead, it describes gradual changes in how statistical meaning is carried: first mainly by text and layout, then increasingly by marks.

For Dataface, this spectrum is most useful as a framing device for choosing and defining families of style presets. It helps explain why some presets should preserve more tabular scaffolding while others should devote more of the canvas to marks.

What Style Presets Control

Style presets should hold chart-skeleton defaults such as:

  • axis side and axis label angle defaults
  • whether grids/domains exist at all
  • legend placement
  • title anchor / title positioning defaults
  • other pre-Vega-Lite chart scaffolding choices

In catalog terms, style presets are the default owner for properties such as:

  • style.legend.disable
  • style.axis.grid
  • style.axis_x
  • style.axis_y
  • style.orientation
  • style.header_overflow
  • spark-bar layout defaults such as style.max_bars, style.labels_hidden, style.counts_hidden, and style.bar_height
  • axis leaf properties like AxisConfig.tickCount, AxisConfig.labelAngle, AxisConfig.labelPadding, and AxisConfig.orient

Concrete examples moved into style presets include:

  • y-axis on the left or right
  • x-axis orientation
  • whether grid lines exist
  • whether the axis domain exists
  • legend placement
  • title anchoring
  • default axis label angle, baseline, and alignment
  • axis title alignment / angle / x-y offsets
  • whether band grids exist

Using a Style Preset

You can pick a style preset in dataface.yml:

style_preset: editorial
theme: light

This keeps the concerns separate:

  • style_preset controls the chart skeleton
  • theme controls the visual skin

Custom Style Presets

You can add your own style presets in dataface.yml and inherit from existing ones:

style_preset: my-editorial

style_presets:
  my-editorial:
    extends: editorial
    vega:
      config:
        axisY:
          orient: left
        legend:
          orient: bottom

When To Use Theme vs Style Preset

Use a theme when you are changing:

  • palette
  • font family
  • font size
  • fill/stroke colors
  • stroke width
  • symbol size
  • background/surface colors
  • table and variable styling

Use a style preset when you are changing:

  • whether the y-axis sits on the left or right
  • whether an element exists at all, like a grid or domain
  • whether grids/domains are shown
  • legend placement
  • title anchoring
  • default axis label angle

Keep the property chart-owned when you are changing:

  • field bindings such as x, y, color, theta, size, or shape
  • chart semantics such as type, KPI value, sort, filters, or href
  • explicit chart framing choices such as a one-off scale domain or custom tooltip encoding

Keep the property in none when it should not be centralized at all:

  • compatibility surfaces like series
  • mixed parent containers like style and mark

Custom Themes

You can create custom themes in your dataface.yml config file:

# dataface.yml
themes:
  my-brand:
    background: "#f8f9fa"
    title:
      color: "#2c3e50"
    axis:
      gridColor: "#ecf0f1"
      labelColor: "#7f8c8d"
    bar:
      fill: "#3498db"
    range:
      category:
        - "#3498db"
        - "#e74c3c"
        - "#2ecc71"
        - "#f39c12"

Then use it:

theme: my-brand

If you want the custom theme to have a preferred paired style preset for internal use, set that separately in dataface.yml:

theme_presets:
  my-brand: my-editorial

This is most useful when you want a theme selection to carry a shared chart scaffolding preset without duplicating those preset keys in the theme file.

Use style_preset: when you want to control how charts are put together without coupling those decisions to a visual brand preset:

title: "Vega-Lite Explorer"
theme: vega-lite-explorer
style_preset: vega-lite-explorer

Use the -rich pair when you want a more complete reference scaffold with many more commented Vega-Lite sections:

theme: vega-lite-explorer-rich
style_preset: vega-lite-explorer-rich

Example project style presets:

style_presets:
  dense-editorial:
    extends: vega-lite-explorer
    concat:
      spacing: 20
    axisBand:
      tickExtra: true
      bandPosition: 0.5
    bar:
      discreteBandSize: 18

Full Vega-Lite Config Pass-Through

Theme objects are open-ended Vega-Lite config objects. Dataface reads a small subset for its own table/variable color derivation, but it passes through any valid Vega-Lite config keys you add.

You can use this in either place:

  • vega.config in dataface.yml for project-wide defaults
  • themes.<name> in dataface.yml for reusable theme presets

Example:

# dataface.yml
vega:
  config:
    axisBand:
      tickExtra: true
      bandPosition: 0.5
    header:
      labelFontSize: 12
      titleFontSize: 13

themes:
  newsroom:
    extends: light
    range:
      category:
        - "#1d4ed8"
        - "#ea580c"
        - "#059669"
    point:
      filled: true
      size: 64
    boxplot:
      median:
        color: "#1a1a1a"

defaults/themes/dataface-default.yaml ships with Dataface's default chart house style; the engine maps it to a Vega-Lite config via style_to_vega_lite() at render time. defaults/default_config.yml holds broader app defaults. Use the official Vega-Lite config docs as the full reference and layer your overrides in dataface.yml.

Built-ins are assembled first, then your dataface.yml themes: block deep-merges on top. That means you can either add a brand-new theme or override part of a built-in one without copying the whole thing.

Theme Inheritance

Themes can extend other themes using extends:

# dataface.yml
themes:
  my-dark-brand:
    extends: dark
    bar:
      fill: "#e74c3c"  # Override just the bar color
    range:
      category:
        - "#e74c3c"
        - "#3498db"

Global Styling

Set styling options for the entire dashboard:

style:
  palette: "category10"         # Color scheme name
  font: "Inter"                  # Font family
  axis:
    grid:
      hidden: false              # Show grid lines by default
  background: "#f5f5f5"          # General background color
  backgrounds:                   # Format-specific backgrounds
    svg: transparent
    png: "#ffffff"
    pdf: "#ffffff"
  css: "assets/custom.css"       # Import custom CSS file

Style Options

  • palette: Color scheme (e.g., "category10", "viridis", "blues")
  • font: Font family name
  • axis.grid.hidden: true to hide grid lines by default
  • background: General background color (hex, rgb, name, or "transparent")
  • backgrounds: Format-specific backgrounds (svg, png, pdf)
  • css: Path to a custom CSS file (relative to dashboard file or public root)

CSS Scope & Limitations

The style.css file styles the Dashboard Shell (HTML), but has limitations regarding internal Chart elements (SVG).

What CSS Can Style (HTML)

  • Layout: Rows, columns, grids, margins.
  • Typography: Section titles, markdown text, KPI cards, headers.
  • Interactive Elements: Filter inputs, buttons, tabs.
  • Backgrounds: Page background, section borders, shadows.

What CSS Cannot Style (Charts)

  • Chart Internals: Bars, lines, axes, legends inside the Vega-Lite charts.
  • PDF Exports: Static exports use a different rendering path where external CSS may not apply fully.

Recommendation: - Use Style Options (style.palette, chart.style) for chart colors and axes. - Use Custom CSS for layout, typography, and branding of the surrounding page.


Chart-Level Styling

Override theme for specific charts:

charts:
  custom_chart:
    title: "Custom Styled Chart"
    query: queries.sales
    type: bar
    x: month
    y: total_revenue
    style:
      palette: "viridis"          # Different color scheme
      legend:
        disable: false            # Show legend (default)
      axis:
        grid:
          hidden: true            # Hide grid lines

Style Options

Color Palettes

Choose from built-in color palettes:

  • category10 - Default categorical palette (10 colors)
  • viridis - Perceptually uniform sequential palette
  • blues - Blue sequential palette
  • reds - Red sequential palette
  • greens - Green sequential palette
  • grays - Grayscale palette

Choosing a palette: - Categorical data: Use category10 or other categorical palettes - Sequential data: Use viridis, blues, reds, greens - Accessibility: Consider colorblind-friendly palettes

DFT Palette Direction

Alongside the generic built-in schemes above, DFT now documents its own chart-library palette direction:

  • the default DFT categorical palette is a blue-first ten-color set tuned for calm, editorial multi-series charts
  • blue-strong is the dedicated single-series default color
  • hero-6 is a hero-blue-versus-neutrals palette for charts where one series should dominate

See DFT Color Palettes for the swatch previews and the reasoning pulled together from the broader DFT design notes.

Legend

Control legend display:

style:
  legend:
    disable: true   # Hide legend

Grid Lines

Show or hide grid lines:

style:
  axis:
    grid:
      hidden: false   # Show grid lines
      # hidden: true  # Hide grid lines

Axis Labels

Control axis label display:

style:
  axis:
    labels: true    # Show labels
    # labels: false # Hide labels

Label density

Control how densely labels are packed on an axis. These knobs affect whether labels are shown or hidden when they would collide or overflow:

  • label.overlap — how Vega-Lite resolves overlapping labels ("greedy", "parity", true, false). Default is "greedy" for x-axes.
  • label.separation — minimum pixel gap between adjacent labels (number). Useful when labels sit shoulder-to-shoulder even without bounding-box overlap.
style:
  axis_x:
    label:
      overlap: greedy   # hide overlapping labels automatically
      separation: 4     # require at least 4px between labels
Positioning and boundary

Five knobs control where a label sits relative to its tick and whether it survives clipping at the axis range edges.

bound — hide labels whose bounding boxes overflow the axis range.

Reach for it when a categorical x-axis has one very long label at the first or last position that bleeds into the chart padding or off the canvas. Accepts true (hide any overflow) or a pixel tolerance (hide only when overflow exceeds that many pixels).

charts:
  product_revenue:
    type: bar
    x: product_name     # categorical, some labels very long
    y: revenue
    style:
      axis_x:
        label:
          bound: true   # hide labels that bleed past chart edge

flush — align the first and last labels flush with the scale range rather than centered on their tick.

Reach for it when time-series edge labels ("Jan 2022" / "Dec 2024") get clipped at chart edges. Note: Vega-Lite defaults differ by axis — true for x, false for y. Accepts true/false or a pixel tolerance (flush only when label exceeds by more than that many pixels).

style:
  charts:
    axis_y:
      label:
        flush: true     # opt y-axis into flush alignment (VL default is false)
charts:
  monthly_revenue:
    type: line
    x: month
    y: revenue
    style:
      axis_x:
        label:
          flush: 4      # flush only if label exceeds scale edge by more than 4px

offset — pixel offset of the label from its anchor.

Reach for it when fine-grained nudging is needed — for example when axis_x.offset shifts the axis line itself or when bar-band tick positioning leaves labels misaligned with bar centers. Negative values move labels toward the chart interior; positive toward the chart edge.

charts:
  weekly_signups:
    type: bar
    x: week
    y: signups
    style:
      axis_x:
        label:
          offset: -3    # nudge labels 3px toward chart interior

line_height — line height in pixels for multi-line labels.

Reach for it when label.expr returns an array of strings (multi-line labels). The default is the label font size. Has no effect on single-line labels.

charts:
  monthly_metric:
    type: bar
    x: month
    y: metric
    style:
      axis_x:
        label:
          expr: "split(datum.label, ' ')"   # multi-line via labelExpr
          line_height: 14                     # 14px between lines

anchor — alignment of the label relative to its tick position ("start", "middle", "end").

Different from align (the text-anchor of the label content). anchor is where the label sits relative to the tick. Useful in two concrete cases: (1) anchoring tilted labels to the right edge so the angled tilt visually attaches to the tick, and (2) band-scale charts where labels should start at the band edge rather than center.

charts:
  long_categories:
    type: bar
    x: state_name          # 50 nominal categories
    y: revenue
    style:
      axis_x:
        label:
          angle: -45
          anchor: end      # angled labels attach at their right edge to the tick

Format aliases (style.formats)

Format aliases map short names like currency or compact to D3 format strings. They live in theme YAML — not in engine code — so project themes can override or extend the vocabulary without touching Python.

Built-in aliases (from dataface-default.yaml)

Alias D3 spec Example output
currency $,.2f $1,234.57
percent .1% 12.3%
compact ,.2s 1.5 M
integer ,.0f 1,235
number ,.2f 1,234.57

Using aliases in charts

charts:
  revenue:
    type: bar
    x: month
    y: revenue
    format: currency    # resolves to "$,.2f" via theme cascade

  conversion:
    type: bar
    x: date
    y: rate
    format: percent     # resolves to ".1%" via theme cascade

  raw_d3:
    type: bar
    x: month
    y: amount
    format: "$,.0f"     # raw D3 spec — passes through unchanged

Defining custom aliases

Add style.formats at the theme or face level. Face keys override theme keys; theme keys not redefined by a face propagate through unchanged.

In a theme YAML:

style:
  formats:
    currency: "$,.2f"
    revenue: "$~s"       # project-specific alias
    arr: "$,.0f"

In a face YAML (overrides theme for this face only):

style:
  formats:
    currency: "$,.0f"    # override theme's 2-decimal currency

Error contract

If an alias key is not defined in the theme cascade and is not a valid D3 format spec, D3 raises at render time. There is no code-side fallback table. This is intentional: the error points clearly at the theme that should define the alias.

Suppressing format with null

Use format: null (or omit format:) to suppress format emission and let VL/D3 pick its own default based on the axis scale type. This is the canonical way to override an inherited theme-level format: compact per chart — no format: auto string sentinel.


Typography

Set the font family via the top-level style configuration:

style:
  font: "Inter"  # Font family name

Common font choices: - Inter - Modern, readable sans-serif - Roboto - Google's Material Design font - Open Sans - Friendly, readable sans-serif - Lato - Humanist sans-serif


Board-Level Styling

Apply CSS-like styles to any board or nested layout section:

rows:
  - title: "Styled Section"
    style:
      background: "#f0f4f8"
      border: "2px solid #667eea"
      border-radius: "8px"
      padding: "16px"
      color: "#333"
    cols:
      - chart: my_chart

Supported Style Properties

Property Example SVG HTML Description
background "#f5f5f5" Background color (hex, rgb, named)
border "2px solid #ddd" Border shorthand (width style color)
border-radius "8px" Corner rounding
color "#333" Text color
padding "16px" Inner spacing (affects content layout)
margin "8px 16px" Outer spacing
gap "12px" Override gap between child items

Padding & Sizing

Padding is accounted for in layout calculations. When you add padding to a board:

  1. The board's children have less available space
  2. Content is properly inset from the border
  3. Height calculations include the padding
# Padding formats (CSS-style)
style:
  padding: "16px"           # All sides equal
  padding: "8px 16px"       # Vertical, horizontal
  padding: "8px 16px 12px"  # Top, horizontal, bottom
  padding: "8px 16px 12px 4px"  # Top, right, bottom, left

Border Styling

Borders support the CSS shorthand format:

style:
  border: "2px solid #667eea"  # width style color
  border-radius: "8px"         # Rounded corners

Example: Styled Cards

Create card-style sections with backgrounds and borders:

cols:
  - text: "### Success Card"
    style:
      background: "#c8e6c9"
      border: "2px solid #4caf50"
      border-radius: "12px"
      color: "#1b5e20"

  - text: "### Warning Card"
    style:
      background: "#fff3e0"
      border: "2px solid #ff9800"
      border-radius: "12px"

Layout Sizing

Automatic Sizing

Dataface automatically calculates sizes based on content:

  • Charts: 300px default height (KPIs: 100px, Tables: 250px)
  • Titles: Height based on font size and text length
  • Markdown content: Height based on rendered text with word-wrapping

User-Specified Widths

In cols layouts, specify widths for individual items:

cols:
  - width: "30%"    # 30% of available width
    chart: sidebar_chart
  - width: "70%"    # 70% of available width
    chart: main_chart

Supported formats: - Percentages: "30%", "70%" - Pixels: "200px", "400px" - Auto (default): Remaining space divided equally

Gap Control

Control spacing between items:

# Per-section gap override
rows:
  - title: "Compact Section"
    style:
      gap: "8px"  # Smaller gap between children
    cols:
      - chart: a
      - chart: b

Content-Aware Heights

Heights are calculated based on content type:

Content Type Default Height
Standard chart 300px
KPI card 100px
Table 250px
Title Based on text
Markdown Based on rendered content

In cols layouts, all items get the same height (maximum of their content heights) for proper alignment.


Number Formatting

Dataface uses d3-format spec strings everywhere numbers appear — chart axis labels, KPI tiles, and table cells all share the same format spec language.

Format spec syntax

The d3-format spec grammar is:

[[fill]align][sign][symbol][0][width][,][.precision][~][type]
Token Meaning Example
fill Padding character (default space) 0>10.2f
align > right · < left · ^ center · = sign-then-pad <10.2f
sign - minus-only · + always · ( parens negatives · space +,.2f
symbol $ currency prefix $,.2f
0 Zero-pad to width 06.2f
width Minimum output width 10.2f
, Thousands separator ,.2f
.precision Decimal digits (or significant figures for s, g, r) .2f
~ Trim trailing zeros and decimal point .1~%
type Format type — see table below f

Supported types (v1):

Type Description Spec Result
f Fixed-point ,.2f on 1234.56 1,234.56
% Percentage (×100 + %) .1~% on 0.05 5%
e Exponential .2e on 1234.56 1.23e+3
s SI prefix ~s on 1500000 1.5M
g General (shorter of e/f) .3g on 0.001234 0.00123
r Rounded significant digits .2r on 12.56 13
d Integer d on 12.9 13
n Locale number (same as ,g) n on 1234 1,234

No-silent-drops contract. Any spec feature outside the v1 supported set raises D3FormatError with a position offset and a list of supported types. A bad spec in a theme or face raises at render time on the first row that uses it — not silently dropped.

B-for-billion divergence. Dataface's Python formatter (KPI tiles and table cells) maps the SI giga prefix G to B so that 1.5G displays as 1.5 B. Chart axes use real d3.js in the browser, which emits G. This is a deliberate Dataface extension documented in D-015. The ~s compact format rounds to 2 significant figures and uses k/M/B/T with a space separator in Dataface (vs k/M/G/T with no space in d3).

For the full d3-format reference see https://d3js.org/d3-format.


Best Practices

Consistent Styling

  • Use the same color palette throughout a dashboard
  • Keep font choices consistent
  • Use grid lines consistently (all on or all off)

Accessibility Considerations

  • Choose colorblind-friendly palettes
  • Ensure sufficient contrast
  • Use labels and legends for clarity

Color Choices

  • Categorical data: Use distinct colors (category10)
  • Sequential data: Use gradient palettes (viridis, blues)
  • Avoid: Too many colors (5-7 max for clarity)

Performance

  • Styling has minimal performance impact
  • Use global style when possible (more efficient)
  • Override only when necessary