Skip to content

Dataface YAML Style Guide

A comprehensive guide for writing clean, maintainable, and consistent Dataface board YAML files.


Table of Contents

  1. Naming Conventions
  2. Structure & Organization
  3. Type-Based Field Encapsulation
  4. Referencing & Modularity
  5. Templating & Expressions
  6. Formatting & Syntax
  7. Best Practices

Naming Conventions

Auto-Interpreted Titles from IDs/Slugs

Principle: Titles and names should be automatically interpreted from IDs/slugs when not explicitly provided.

Rule: If an object has an id or name field (or is a file-based board) but no title, the title should be auto-generated by converting the identifier to a human-readable format.

  • For file-based boards, the filename (without extension) is the identifier.
  • For nested boards, the explicit id or name (if provided) is the identifier.

Examples:

# ✅ Good: Title auto-generated from filename 'sales_overview.yml'
# File: sales_overview.yml
# Implicit Title: "Sales Overview"
rows:
  - title: "Q1 Details"
    # ...

# ✅ Good: Explicit title overrides auto-generation
# File: sales_overview.yml
title: "Sales Dashboard - Q4 2024"

Auto-Generation Rules: - Convert snake_case to Title Case: revenue_by_monthRevenue By Month - Convert camelCase to Title Case: revenueByMonthRevenue By Month - Preserve acronyms: kpi_revenueKPI Revenue

Identifier Naming

Board File Names:

# ✅ Good: lowercase with underscores
sales_overview.yml
monthly_sales_board.yml

# ⚠️ Discouraged (but supported): spaces, special characters, camelCase
Sales Overview.yml
monthlySalesBoard.yml
sales-board.yml
# These will still be correctly converted to titles (e.g. "Sales Overview", "Monthly Sales Board")
# but standardizing on snake_case is recommended for consistency.

Query Names:

# ✅ Preferred: Descriptive names without prefixes
queries:
  sales: ...
  sales_by_region: ...
  product_categories: ...

# ✅ Preferred: Use explicit namespacing when referencing queries
query: queries.sales  # Explicit - clearly a query
query: queries.totals  # Explicit - unambiguous

# ✅ Also acceptable: Simple reference when context is clear
query: sales  # Works when unambiguous, but explicit is preferred

Chart IDs:

# ✅ Good: descriptive, indicates purpose
charts:
  revenue_by_month: ...
  sales_trend: ...
  kpi_total_revenue: ...

# ❌ Bad: generic, unclear purpose
charts:
  chart1: ...
  chart: ...
  c1: ...

Variable Names:

# ✅ Good: clear, descriptive
variables:
  region: ...
  date_range: ...
  min_revenue: ...

# ❌ Bad: abbreviations, unclear
variables:
  reg: ...
  dr: ...
  min_rev: ...


Structure & Organization

Top-Level Organization

Recommended Order: 1. Board metadata (title, description - name is file-based) 2. Variables (user inputs/filters) 3. Queries (data definitions) 4. Charts (reusable chart definitions - optional) 5. Boards (nested boards/sections)

Example:

# File: sales_dashboard.yml
title: "Sales Dashboard"
description: "Monthly sales metrics and trends"

variables:
  # User inputs
  region: ...
  date_range: ...

queries:
  # Data queries
  sales: ...
  regional: ...

charts:
  # Optional: reusable chart definitions
  revenue_chart: ...

rows:
  # Nested boards (previously sections)
  - title: "Overview"
    cols: ...

Group by Domain:

queries:
  # Sales queries
  sales: ...
  sales_by_region: ...
  sales_trends: ...

  # Product queries
  products: ...
  product_categories: ...

  # Customer queries
  customers: ...
  customer_segments: ...

Use Comments for Organization:

rows:
  # Overview board - high-level KPIs
  - title: "Overview"
    cols: ...

  # Trends board - time series analysis
  - title: "Trends"
    rows: ...

  # Details board - detailed tables
  - title: "Details"
    rows: ...


Type-Based Field Encapsulation

Principle: When an object has different fields per "type", use the type as the encapsulating key for those fields.

Rule: Type-specific fields should be nested under a key matching the type name. The presence of a type-specific key is sufficient to determine the type - no explicit layout:, type:, input:, or data_type: fields are needed.

Board Layout Types

Row Layout (presence of rows: key determines layout - stacks vertically):

rows:
  - title: "Row 1"
    cols: ...
  - title: "Row 2"
    cols: ...

Column Layout (presence of cols: key determines layout - stacks horizontally):

cols:
  - title: "Sidebar"
    width: "300px"
    rows: ...
  - title: "Main Content"
    rows: ...

Grid Layout (presence of grid: key determines layout):

grid:
  columns: 12        # 12-column grid
  row_height: 100px
  gap: md            # Spacing: sm, md, lg, xl, or none
  default_width: 6   # Fallback width if no smart default exists
  default_height: 4  # Fallback height
  items:
    - kpi_revenue      # Smart Sizing: Automatically 2x2 (KPI type)

    - chart1           # Flowing item: Uses defaults (6x4), placed automatically

    - width: 12        # Overrides width (full row)
      height: 6        # Overrides height
      item: chart2

    - x: 0             # Pinned item: Explicit position
      y: 10            # Starts at row 10
      width: 4
      height: 4
      item: chart3

    - x: 2             # Overlapping item: Can share space with others
      y: 10
      width: 2
      item: chart4_overlay

Tab Layout (presence of tabs: key determines layout):

tabs:
  default: "overview"
  position: top
  boards:
    - title: "Overview" # Tab 1 (Nested Board)
      rows: ...
    - title: "Details"  # Tab 2 (Nested Board)
      cols: ...

Default Layout: If no layout-specific key is present, row layout is used by default.

Query Types

Semantic Layer Query (presence of metrics: and/or dimensions: keys determines type):

queries:
  sales:
    metrics: [total_revenue]
    dimensions: [month]

dbt Model Query (presence of model: and columns: keys determines type):

queries:
  products:
    model: "ref('fct_products')"
    columns: [product_id, name, price]

Raw SQL Query (presence of sql: key determines type):

queries:
  custom:
    sql: |
      SELECT * FROM orders
      WHERE created_at >= {{ date_range.start }}
      LIMIT 100
    profile: my_postgres  # Required: dbt profile name

Type Inference Rules (checked in order): 1. If metrics: and/or dimensions: are present → Semantic Layer Query 2. If model: and columns: are present → dbt Model Query 3. If sql: is present → Raw SQL Query 4. If query value is a string (no other keys) → Raw SQL Query (default)

Note: SQL is the default query type, making it the most concise option for raw SQL queries. This covers the majority of use cases where you just need to write SQL directly.

Variable Input Types

Select Input (presence of select: key determines input type):

variables:
  region:
    select:
      options:
        static: ["North", "South", "East", "West"]
      placeholder: "Choose a region"
    default: "North"

Slider Input (presence of slider: key determines input type):

variables:
  min_revenue:
    slider:
      min: 0
      max: 1000000
      step: 1000
    default: 10000

Date Range Input (presence of daterange: key determines input type):

variables:
  date_range:
    daterange:
      format: "YYYY-MM-DD"
      presets: ["last_7_days", "last_30_days", "last_quarter"]
    default: "2024-01-01"

Type Inference Rules: - The presence of type-specific keys (select:, slider:, daterange:, datepicker:, checkbox:, radio:, input:, textarea:, multiselect:) determines the input type - Data type is inferred from the input type (e.g., slider:number, daterange:date) - No need for explicit input: or data_type: fields

Benefits of Type Encapsulation: - Clear separation of type-specific fields - Prevents field name conflicts - Makes schema validation easier - Improves IDE autocomplete support - Self-documenting structure - Reduces verbosity - no need for explicit type declarations when structure implies type - More intuitive - structure itself communicates intent

Key Principle: The structure itself is the type declaration. If you see grid: in a section, it's a grid layout. If you see sql: in a query, it's a SQL query. If you see daterange: in a variable, it's a date range input.


Referencing & Modularity

Principle: Allow referencing from higher-level structures or external files for flexible organization, while also permitting definitions at lower levels for convenience.

Hierarchical References

Charts at Board Level:

# File: sales_overview.yml
charts:
  revenue_chart:
    title: "Revenue"
    query: sales
    type: bar

rows:
  - revenue_chart

Charts at Nested Board Level:

# File: sales_overview.yml
rows:
  - charts:
      # Define charts locally in this nested board
      revenue_chart:
        title: "Revenue"
        query: sales
        type: bar
      orders_chart:
        title: "Orders"
        query: sales
        type: line

    cols:
      # Reference the locally defined charts
      - revenue_chart
      - orders_chart

Queries at Board Level:

# File: sales_overview.yml
queries:
  sales:
    metrics: [total_revenue]

rows:
  - title: "Revenue"
    # Reference query from board level
    query: sales
    type: bar

Queries at Chart Level:

# File: sales_overview.yml
rows:
  - title: "Revenue"
    # Define query inline at chart level
    query:
      metrics: [total_revenue]
      dimensions: [month]
    type: bar

External File References

Principle: Dataface operates on a Project Context. All YAML files in your project are automatically loaded and available as namespaces. You don't need explicit import statements; the filename itself is the namespace.

Syntax: filename.resource_id * filename: The name of the file without extension (e.g., _shared for _shared.yml) * resource_id: The key of the resource within that file (e.g., sales)

Note on Uniqueness: Resource IDs (queries, charts, variables) must be unique within a file and across all imported files. Each type (queries, charts, variables) has its own namespace, so you can have a query and chart with the same name. However, duplicate names within the same type (e.g., two queries both named sales) are not allowed, even if they're in different files that are imported together. If you have a naming collision, use explicit namespacing: queries.sales vs charts.sales.

Explicit Referencing (Optional): You can also use the explicit path filename.type.id if you prefer clarity or need to resolve ambiguity. - _sales_queries.queries.sales - _sales_charts.charts.revenue_trend

Referencing External Queries:

# File: _sales_queries.yml
queries:
  sales: ...

# File: sales_overview.yml
rows:
  - title: "Revenue"
    query: _sales_queries.sales  # 'sales' from '_sales_queries.yml'
    type: bar

Note on Implicit Namespacing: This "Project Context" approach treats filenames as global variables. While convenient, it can feel "magical" compared to explicit imports (like dbt's ref() or Python's import). - Benefit: Reduces boilerplate; valid YAML structure (no jinja brackets needed). - Risk: Potential naming collisions between local variables and filenames. - Best Practice: Use unique, descriptive filenames (especially for partials using _) to avoid conflicts with local keys.

Referencing External Charts:

# File: _shared_charts.yml
charts:
  revenue_trend:
    type: line
    ...

# File: sales_overview.yml
rows:
  - _shared_charts.revenue_trend  # Reference namespaced chart

Referencing External Variables:

# File: _common_vars.yml
variables:
  region: ...

# File: sales_overview.yml
queries:
  sales:
    filters:
      region: _common_vars.region

Reusing Nested Boards: You can reference a specific section (nested board) of another file if you give it an ID.

# File: sales_dashboard.yml
rows:
  - id: kpi_section  # Give the section an ID
    title: "KPIs"
    cols: ...

# File: executive_summary.yml
rows:
  - sales_dashboard.kpi_section  # Reference the section by ID

When to Use Each Approach

Use Top-Level Definitions When: - Charts/queries are reused across multiple nested boards - You want centralized organization - Building complex boards with many components

Use Nested Definitions When: - Charts are specific to one nested board - You want to keep related code together - Building simple boards

Use External Partial References When: - Sharing components across multiple board files - Organizing large boards into modules - Building a library of reusable components

Example: Hybrid Approach:

# File: sales_overview.yml

# Shared queries used across nested boards
queries:
  sales: ...
  products: ...

# Shared charts used in multiple nested boards
charts:
  revenue_chart: ...

rows:
  - title: "Overview"
    cols:
      # Mix of references and inline definitions
      - revenue_chart  # Reference from top level
      - orders_chart:  # Inline definition
          query: sales
          type: bar

  - title: "Details"
    rows:
      # Inline query definition for specific chart
      - detail_chart:
          query:
            metrics: [total_revenue]
            dimensions: [day, product]
          type: table


Templating & Expressions

Principle: Use Jinja2 templating for dynamic values, logic, and variable substitution. Templating is allowed in SQL strings, text content, labels, and filter values.

Basic Syntax

  • Output: {{ variable }} - Renders the value of a variable or expression.
  • Logic: `` - Control flow (if/else, loops).
  • Comments: `` - Comments that won't be rendered.

Referencing Variables

# ✅ Good: Direct variable reference in SQL
queries:
  sales:
    sql: |
      SELECT * FROM orders
      WHERE region = '{{ region }}'
    profile: my_postgres

# ✅ Good: Using variables in markdown content
rows:
  - title: "Overview for {{ region }}"
    content: |
      Showing sales data for **{{ region }}** from {{ date_range.start }} to {{ date_range.end }}.

Logic and Control Flow

Use logic to make queries or content dynamic based on inputs.

# ✅ Good: Conditional SQL generation
queries:
  dynamic:
    sql: |
      SELECT * FROM sales
      {% if region != 'All' %}
        WHERE region = '{{ region }}'
      {% endif %}
    profile: my_postgres

Best Practices

  1. Keep Logic Simple: Avoid complex business logic in templates. Move complex logic to dbt models or database views if possible.
  2. Quote Strings: Remember to quote string variables in SQL: '{{ region }}'.
  3. Use Filters: Use Jinja filters for formatting: {{ revenue | number_format }}.

Formatting & Syntax

Indentation

Use 2 spaces (not tabs) for indentation:

# ✅ Good: 2 spaces
title: "Sales Dashboard"
queries:
  sales: ...

# ❌ Bad: tabs or 4 spaces
title: "Sales Dashboard"
    queries: ...

Lists vs Maps

Use Lists for Ordered Collections:

# ✅ Good: list for ordered sections
rows:
  - title: "First Section"
    cols: ...
  - title: "Second Section"
    cols: ...

Use Maps for Named Collections:

# ✅ Good: map for named queries
queries:
  sales: ...
  products: ...

# ✅ Good: map for named charts
charts:
  revenue_chart: ...
  orders_chart: ...

String Formatting

Use Quotes When Needed:

# ✅ Good: quotes for strings with special characters
title: "Sales Dashboard - Q4 2024"
description: "Dashboard showing sales metrics"

# ✅ Good: no quotes for simple strings
name: sales_dashboard
type: bar

# ✅ Good: quotes for strings starting with numbers
id: "2024_sales"

Multi-line Strings:

# ✅ Good: use | for literal block scalar
content: |
  # Sales Performance

  This quarter showed strong growth.

  Key highlights:
  - Revenue up 15%
  - Orders increased 20%

# ✅ Good: use > for folded block scalar (single line)
description: >
  This dashboard shows monthly sales metrics
  and trends by region.

Arrays

Inline Arrays:

# ✅ Good: inline for short arrays
metrics: [total_revenue, order_count]
dimensions: [month, region]

# ✅ Good: multi-line for longer arrays
metrics:
  - total_revenue
  - order_count
  - avg_order_value
  - customer_count

Comments

Use Comments Liberally:

# File: sales_dashboard.yml
title: "Sales Dashboard"

# User input filters
variables:
  region: ...

# Data queries - grouped by domain
queries:
  # Sales queries
  sales: ...

  # Product queries
  products: ...

# Board layout
rows:
  # Overview board
  - title: "Overview"
    charts: ...


Best Practices

1. Start Simple, Add Complexity Gradually

# ✅ Good: Start with minimal required fields
# File: sales_dashboard.yml
queries:
  sales:
    metrics: [total_revenue]
    dimensions: [month]

charts:
  revenue_chart:
    query: sales
    type: bar

rows:
  - revenue_chart

Then add features as needed: - Add titles when auto-generated ones aren't sufficient - Add variables when interactivity is needed - Add styling when default appearance needs customization

2. Keep Layouts Clean with References

Principle: Strictly separate Definition from Layout. - Definitions: Define charts and queries in charts: and queries: sections. - Layout: Use rows, cols, and grid only to arrange references or nested boards.

Avoid defining charts inline within layout arrays. This keeps the visual structure clean and readable.

# ✅ Good: Layout contains only references and structural boards
charts:
  revenue_chart: ...
  orders_chart: ...

rows:
  - cols:
      - revenue_chart
      - orders_chart
  - title: "Trends"  # Nested Board
    rows: ...

# ⚠️ Discouraged: Inline chart definitions clutter the layout
rows:
  - type: bar        # Inline chart
    query: sales

3. Use Explicit Tags for Clarity

Principle: If a complex inline structure feels ambiguous or confusing, use YAML Tags (!Chart, !Board, !Query) to make the type explicit.

rows:
  - !Board
    title: "Complex Nested Section"
    charts:
      local_chart: !Chart
        type: bar
        query: sales
    cols:
      - local_chart

4. Use Descriptive Names

# ✅ Good: Clear, descriptive names
queries:
  sales_by_region_and_month: ...
charts:
  revenue_trend_by_region: ...

# ❌ Bad: Unclear abbreviations
queries:
  sbr: ...
charts:
  rtbr: ...
# ✅ Good: Grouped by domain
queries:
  # Sales domain
  sales: ...
  sales_by_region: ...
  sales_trends: ...

  # Product domain
  products: ...
  product_categories: ...

# ❌ Bad: Mixed organization
queries:
  sales: ...
  products: ...
  sales_by_region: ...
  product_categories: ...

5. Use Type Encapsulation Consistently

# ✅ Good: Type-specific fields under type key
rows:
  - grid:
      columns: 12
      gap: md
    items:
      - width: 6
        item: chart1

# ❌ Bad: Type-specific fields at wrong level
rows:
  - grid:
      columns: 12  # Should be under 'grid'
    items: ...
    gap: md        # Should be under 'grid'

6. Document Complex Logic

# ✅ Good: Comments explain complex logic
queries:
  filtered_sales:
    metrics: [total_revenue]
    dimensions: [month]
    filters:
      # Use 'All' to show all regions, otherwise filter by selected region
      region: "{{ region }}"
      # Date range filter using variable
      order_date: "{{ date_range }}"

7. Validate Early and Often

# ✅ Good: Validate YAML syntax and structure
# Use: df validate sales_dashboard.yml

# ✅ Good: Test incrementally
# Start with minimal board, add features one at a time

Summary

This style guide establishes conventions for writing clean, maintainable Dataface dashboard YAML files. Key takeaways:

  1. Auto-generate titles from IDs/slugs when not explicitly provided
  2. Use type encapsulation for type-specific fields (e.g., grid:, rows:, cols:, tabs:)
  3. Keep layouts clean by using references instead of inline definitions
  4. Support flexible referencing - allow definitions at multiple levels and external files
  5. Follow consistent naming conventions (snake_case for identifiers)
  6. Organize logically - group related elements, use comments
  7. Format consistently - 2-space indentation, clear structure
  8. Start simple - add complexity only when needed

By following these conventions, your Dataface dashboards will be: - Readable: Easy to understand at a glance - Maintainable: Easy to modify and extend - Reusable: Components can be shared across dashboards - Consistent: Predictable structure across projects


Validating Your YAML

After writing your dashboard YAML, validate it to catch syntax and structure errors:

face validate dashboards/sales.yml

For strict validation that fails on warnings:

face validate dashboards/sales.yml --strict

See the CLI Reference for more validation options.