Quick Guide¶
Dataface faces are YAML files that render as boards. This guide shows the minimal setup, the core face syntax, and the commands you use to render or serve your work.
Setup¶
Install Dataface, then scaffold a project with dft init:
dft init writes a dataface.yml project anchor and a faces/ directory with a
starter dashboard (faces/guide.yaml) that renders without a database. Preview it
right away with dft serve, then edit the starter or add your own face alongside
it. dft init can also set up editor and AI integrations.
If you are starting in an existing dbt project, run dft init in the project
root: dft serve discovers the root from dbt_project.yml, and query sources
come from your dbt profiles.yml.
See Getting Started for the full setup path.
Syntax¶
The board is the base element: a layout tree that can hold text, variables, queries, charts, and other boards. Start with board layout, then add the pieces that make a board interactive.
Boards¶
A board can be as small as a title and Markdown text:
title: "Simple board"
text: |
This is Markdown text.
- **bold** for important points
- *italics* for nuance
- `code` for field names
Boards become powerful because they nest. When you need structure, rows
stacks items vertically, cols places them side by side, and each item is
itself a board:
title: "This is the parent board"
width: 600
rows:
- text: |
With just **some text** on it. It's Markdown, so we can put things like
[links](https://docs.it-dataface.com/), *emphasis*, and lists:
- keep prose close to the data
- call out important details
- add structure without adding a chart
style:
border:
width: 1
color: "#cbd5e1"
- title: "Nested board"
text: |
This is a nested board with its own border.
style:
border:
width: 1
color: "#cbd5e1"
- title: "Two nested boards"
cols:
- text: |
And here's another with just text, no title.
- **Bold text** works inside nested boards.
- *Italic text* does too.
- Bullets make short notes easy to scan.
style:
border:
width: 1
color: "#cbd5e1"
- title: "And another with just a title"
style:
border:
width: 1
color: "#cbd5e1"
When a face defines charts but no explicit layout, Dataface renders those charts
as implicit rows. Use rows, cols, tabs, or grid when the arrangement
matters.
See Boards for layout patterns.
Variables¶
Variables are the controls readers use to change a board. This one creates a segment selector with three static options:
variables:
segment:
input: select
options:
static:
- Enterprise
- SMB
- Mid-Market
text: |
# Segment: {{ segment or 'All segments' }}
Change the selector above. The text reads **{{ segment or 'All segments' }}**
from the current variable value.
The {{ segment }} expression can appear in text, titles, links, SQL, and
query filters. A variable does not filter data by itself; you wire it into a
query explicitly.
Variables are usually not static like this first example. In production, you often populate them from a query, a source column, a dbt model column, or a MetricFlow dimension or measure; they also support many UI element and styling options. Read more in Variables.
Queries¶
Queries define the data a board can use. A face can mix database-backed SQL queries with static values for targets, labels, or small reference tables:
queries: revenue: # sql to a database sql: | SELECT month, segment, revenue FROM mart_revenue ORDER BY month source: analytics targets: # static values type: values columns: [segment, target] values: - [Enterprise, 438000] - [SMB, 228000] - [Mid-Market, 285000]
Queries can use the same {{ segment }} variable from above. The filter
helper writes the SQL predicate for the selected value, or a no-op predicate
when no segment is selected:
queries: revenue: sql: | SELECT month, segment, revenue FROM mart_revenue WHERE {{ filter('segment', segment) }} ORDER BY month source: analytics
Each query has a name (revenue, targets) and returns named columns.
Queries can reference other queries, read previous query results, tune cache
behavior, and combine SQL with CSV, dbt model data, MetricFlow data, HTTP, and
static values. Read more in Queries.
Charts¶
Charts reference queries and map result columns to visual channels. Start with a query, then bind its columns to a chart:
queries:
monthly_revenue:
sql: |
SELECT date, SUM(revenue) AS revenue
FROM ecommerce_orders
GROUP BY date
ORDER BY date
source: examples_db
charts:
revenue_trend:
query: monthly_revenue
type: line
title: "Revenue trend"
x: date
y: revenue
The chart does not aggregate or infer meaning. The query owns the rows and
columns; the chart owns the visual encoding. Here, the SQL groups orders by
date, and the line chart maps date to the x-axis and revenue to the y-axis.
Variables can then feed the same query layer. This example populates a category selector from SQL and uses the selected value in the revenue query:
variables:
category:
input: select
options:
query: category_options
queries:
category_options:
sql: |
SELECT DISTINCT category AS value
FROM ecommerce_orders
ORDER BY category
source: examples_db
revenue:
sql: |
SELECT category, SUM(revenue) AS revenue
FROM ecommerce_orders
WHERE {{ filter('category', category) }}
GROUP BY category
ORDER BY revenue DESC
source: examples_db
charts:
revenue_by_category:
query: revenue
type: bar
title: "Revenue by category"
x: category
y: revenue
Here, the variable changes the SQL predicate, while the chart still only maps the category and revenue columns to the axes.
Charts come in many families, and extensible charts can render custom SVG or HTML when the built-in families are not enough. Dive into Charts.
Styling¶
Dataface keeps styling declarative too. Choose a theme for the whole board,
then use typed chart options for labels, axes, formats, colors, marks, and
scale behavior. Built-in themes include default, cream, stark, dark,
light, looker, and carbong100; chart-local style only needs to override
what should differ from the theme:
title: "Styled revenue trend"
theme: cream
width: 600
queries:
monthly_revenue:
sql: |
SELECT date, SUM(revenue) AS revenue
FROM ecommerce_orders
GROUP BY date
ORDER BY date
source: examples_db
charts:
revenue_trend:
query: monthly_revenue
type: line
title: "Revenue trend"
x: date
y: revenue
x_label: Order day
y_label: Daily revenue
style:
number_format: currency_whole
marks:
line:
stroke:
color: "#b45309"
width: 5
curve: step
point:
size: 70
color: "#7c2d12"
filled: true
Use styles when the default rendering needs more polish, but keep data meaning in the query and visual mapping in the chart definition. See Styling and the chart family pages for the full option set.
Bringing It All Together Cheat Sheet¶
Start with the same pattern in a small board: one variable, one query, two KPIs above one single-value chart.
title: "Category snapshot"
width: 600
variables:
category:
label: "Category"
input: select
options:
query: category_options
queries:
category_options:
sql: |
SELECT DISTINCT category AS value
FROM ecommerce_orders
ORDER BY category
source: examples_db
summary:
sql: |
SELECT
SUM(revenue) AS revenue,
COUNT(*) AS orders,
AVG(revenue) AS avg_order
FROM ecommerce_orders
WHERE {{ filter('category', category) }}
source: examples_db
charts:
revenue:
query: summary
type: kpi
label: "Revenue"
value: revenue
style:
value:
format: currency_whole
orders:
query: summary
type: kpi
label: "Orders"
value: orders
avg_order:
query: summary
type: kpi
label: "Avg order"
value: avg_order
style:
value:
format: currency_whole
rows:
- cols:
- revenue
- orders
- avg_order
Dataface is flexible enough that you could use it to write the quick guide
itself. The fuller cheat sheet below uses the same pieces, then nests the
examples so the board reads like a compact version of this page. The parent
board uses the cream theme, then the embedded example boards switch to a white
stark surface so they read as boards nested inside the guide. Notice the chart
definitions live at the top, then nested boards list chart IDs like
revenue_by_month and target_by_month where those charts should appear.
title: "Dataface quick start cheat sheet"
theme: cream
style:
border:
width: 0
radius: 0
variables:
segment:
label: "Segment"
input: select
options:
static: [Enterprise, SMB, Mid-Market]
queries:
revenue:
type: values
columns: [month, segment, revenue, target]
values:
- [Jan, Enterprise, 142000, 130000]
- [Feb, Enterprise, 151000, 138000]
- [Mar, Enterprise, 164000, 146000]
- [Jan, SMB, 68000, 72000]
- [Feb, SMB, 74000, 76000]
- [Mar, SMB, 81000, 80000]
- [Jan, Mid-Market, 97000, 91000]
- [Feb, Mid-Market, 101000, 95000]
- [Mar, Mid-Market, 109000, 99000]
charts:
revenue_by_month:
query: revenue
type: bar
title: "Revenue by month"
x: month
y: revenue
color: segment
target_by_month:
query: revenue
type: line
title: "Target by month"
x: month
y: target
color: segment
rows:
- title: "1. Boards"
cols:
- text: |
A board starts with a `title`, then stacks `rows` or places `cols`
beside each other. Text cells support **markdown**, including links,
lists, emphasis, and inline `code`.
```yaml
title: "Basic board"
rows:
- text: |
This is markdown text.
- **bold** for important points
- *italics* for nuance
- `code` for field names
- cols:
- title: "Left panel"
text: "Boards can sit side by side."
- title: "Right panel"
text: "Each item is itself a board."
```
- theme: stark
style:
background: "#ffffff"
border:
width: 0
radius: 0
title: "Basic board"
rows:
- text: |
This is markdown text.
- **bold** for important points
- *italics* for nuance
- `code` for field names
- cols:
- title: "Left panel"
text: "Boards can sit side by side."
- title: "Right panel"
text: "Each item is itself a board."
- title: "2. Variables"
cols:
- text: |
Variables define controls. This board has a `segment` selector, and
both the text and charts read the current value. The board on the
right is an actual nested board with its own variable definition.
```yaml
variables:
example_segment:
label: "Segment"
input: select
options:
static: [Enterprise, SMB, Mid-Market]
rows:
- text: |
Current segment:
**{{ example_segment or 'All segments' }}**
```
- theme: stark
style:
background: "#ffffff"
border:
width: 0
radius: 0
title: "Rendered variable"
variables:
example_segment:
label: "Segment"
input: select
options:
static: [Enterprise, SMB, Mid-Market]
text: |
Current segment: **{{ example_segment or 'All segments' }}**.
Change this nested board's selector and the text updates with it.
- title: "3. Queries"
cols:
- text: |
Queries own the rows and columns. They can come from SQL, CSV, dbt
models, MetricFlow data, HTTP data, or inline values.
```yaml
queries:
revenue:
type: values
columns: [month, segment, revenue, target]
values:
- [Jan, Enterprise, 142000, 130000]
- [Feb, Enterprise, 151000, 138000]
- [Mar, Enterprise, 164000, 146000]
```
- theme: stark
style:
background: "#ffffff"
border:
width: 0
radius: 0
title: "Query result"
text: |
The `revenue` query in this cheat sheet returns monthly revenue,
segment, and target columns. The chart definitions below decide how to
encode those columns visually.
- title: "4. Charts"
rows:
- text: |
Charts reference a query and bind result columns to visual channels.
The chart definitions are declared once above, then this nested board
lists the chart IDs in `cols` to place them side by side.
```yaml
charts:
revenue_by_month:
query: revenue
type: bar
title: "Revenue by month"
x: month
y: revenue
color: segment
```
- theme: stark
style:
background: "#ffffff"
border:
width: 0
radius: 0
title: "Rendered charts"
cols:
- revenue_by_month
- target_by_month
- title: "5. Nested boards"
cols:
- text: |
Nested boards are how larger dashboards stay organized. This nested
board has its own variable, query, charts, and layout. Inside it,
chart IDs are placed where they belong.
```yaml
title: "Segment review"
variables:
review_segment:
label: "Segment"
input: select
options:
static: [Enterprise, SMB, Mid-Market]
queries:
review_revenue:
type: values
columns: [month, segment, revenue, target]
values:
- [Jan, Enterprise, 142000, 130000]
- [Feb, Enterprise, 151000, 138000]
- [Mar, Enterprise, 164000, 146000]
charts:
review_revenue_by_month:
query: review_revenue
type: bar
x: month
y: revenue
cols:
- text: |
Current segment:
**{{ review_segment or 'All segments' }}**
- review_revenue_by_month
```
- theme: stark
style:
background: "#ffffff"
border:
width: 0
radius: 0
title: "Segment review"
variables:
review_segment:
label: "Segment"
input: select
options:
static: [Enterprise, SMB, Mid-Market]
queries:
review_revenue:
type: values
columns: [month, segment, revenue, target]
values:
- [Jan, Enterprise, 142000, 130000]
- [Feb, Enterprise, 151000, 138000]
- [Mar, Enterprise, 164000, 146000]
- [Jan, SMB, 68000, 72000]
- [Feb, SMB, 74000, 76000]
- [Mar, SMB, 81000, 80000]
- [Jan, Mid-Market, 97000, 91000]
- [Feb, Mid-Market, 101000, 95000]
- [Mar, Mid-Market, 109000, 99000]
charts:
review_revenue_by_month:
query: review_revenue
type: bar
title: "Revenue"
x: month
y: revenue
color: segment
review_target_by_month:
query: review_revenue
type: line
title: "Target"
x: month
y: target
color: segment
cols:
- style:
background: "#ffffff"
border:
width: 0
radius: 0
text: |
Current segment: **{{ review_segment or 'All segments' }}**
- compare revenue against target
- keep the explanation beside the visuals
- style:
background: "#ffffff"
border:
width: 0
radius: 0
rows:
- review_revenue_by_month
- review_target_by_month
Small boards compose into larger reports, and those reports can compose again. When a nested board gets large, move it to its own face YAML file and import it from the layout.
Quick Reference¶
title: "My Board" variables: segment: input: select options: static: [Enterprise, SMB, Mid-Market] queries: revenue: type: values columns: [month, segment, revenue] values: - [Jan, Enterprise, 142000] charts: revenue_by_month: query: revenue type: bar x: month y: revenue rows: - title: "Revenue" text: "Monthly revenue by selected segment." - revenue_by_month
Start with the data and interaction you need, then add the board shape that makes it readable.
Tools¶
Use dft render when you want a static artifact from one face file:
dft render faces/revenue.yml
dft render faces/revenue.yml --format html
dft render faces/revenue.yml --format png --output revenue.png
Use --var to render a specific variable state:
See dft render for every output format.
Use dft serve when you want live browser previews while editing YAML:
Face paths map to URLs. For example, faces/revenue.yml renders at
/revenue/, and query parameters set variables:
See dft serve for routing, ports, and database options.