Appearance
Styling & themes
Themes
The grid ships a light and a dark theme. theme="auto" follows the operating system.
vue
<Datagrid theme="dark" ... />
<Datagrid theme="auto" ... />Source
vue
<template>
<div class="demo">
<div class="demo-toolbar">
<label><input v-model="theme" type="radio" value="light" /> Light</label>
<label><input v-model="theme" type="radio" value="dark" /> Dark</label>
<label><input v-model="theme" type="radio" value="auto" /> System</label>
<label><input v-model="custom" type="checkbox" /> Custom variables</label>
</div>
<Datagrid
:class="{ 'my-grid': custom }"
:theme="theme"
:columns="columns"
:rows="rows"
:options="options"
/>
</div>
</template>
<script setup lang="ts">
import { ref } from "vue";
import { Datagrid, type ColumnConfig, type GridOptions } from "@datagrid/vue-ui";
import { createEmployees, type Employee } from "./data";
const theme = ref<"light" | "dark" | "auto">("light");
const custom = ref(true);
const rows = createEmployees(200);
const columns: Array<ColumnConfig<Employee>> = [
{ field: "firstName", text: "First name" },
{ field: "lastName", text: "Last name" },
{ field: "department", text: "Department" },
{ field: "salary", text: "Salary", type: "number", aggFunc: "sum" },
{ field: "active", text: "Active", type: "boolean" },
];
const options: GridOptions<Employee> = {
checkboxSelection: true,
totalRow: "bottom",
defaultColumn: { sortable: true, filterable: true, flex: 1 },
};
</script>
<style>
.uni-table.my-grid {
--ut-accent-color: #7c3aed;
--ut-font-size: 13px;
--ut-cell-padding-y: 3px;
--ut-header-row-height: 32px;
--ut-radius: 12px;
--ut-header-background: color-mix(in srgb, #7c3aed 8%, var(--ut-background));
}
</style>tsx
import { useState } from "react";
import { Datagrid, type ColumnConfig, type GridOptions } from "@datagrid/react-ui";
import { createEmployees, type Employee } from "../data";
type Theme = "light" | "dark" | "auto";
const rows = createEmployees(200);
const columns: Array<ColumnConfig<Employee>> = [
{ field: "firstName", text: "First name" },
{ field: "lastName", text: "Last name" },
{ field: "department", text: "Department" },
{ field: "salary", text: "Salary", type: "number", aggFunc: "sum" },
{ field: "active", text: "Active", type: "boolean" },
];
const options: GridOptions<Employee> = {
checkboxSelection: true,
totalRow: "bottom",
defaultColumn: { sortable: true, filterable: true, flex: 1 },
};
export default function Theming() {
const [theme, setTheme] = useState<Theme>("light");
const [custom, setCustom] = useState(true);
const themeRadio = (value: Theme, label: string) => (
<label>
<input type="radio" checked={theme === value} onChange={() => setTheme(value)} /> {label}
</label>
);
return (
<div className="demo">
<div className="demo-toolbar">
{themeRadio("light", "Light")}
{themeRadio("dark", "Dark")}
{themeRadio("auto", "System")}
<label>
<input type="checkbox" checked={custom} onChange={(e) => setCustom(e.target.checked)} /> Custom variables
</label>
</div>
<Datagrid
className={custom ? "my-grid" : undefined}
theme={theme}
columns={columns}
rows={rows}
options={options}
/>
<style>{`
.uni-table.my-grid {
--ut-accent-color: #7c3aed;
--ut-font-size: 13px;
--ut-cell-padding-y: 3px;
--ut-header-row-height: 32px;
--ut-radius: 12px;
--ut-header-background: color-mix(in srgb, #7c3aed 8%, var(--ut-background));
}
`}</style>
</div>
);
}CSS custom properties
All colors and sizes are CSS custom properties defined on the element with the class uni-table. Override them for all grids or for a single grid:
css
/* all grids */
.uni-table {
--ut-accent-color: #0969da;
--ut-font-size: 13px;
}
/* a single grid: <Datagrid class="compact" /> */
.uni-table.compact {
--ut-cell-padding-y: 2px;
--ut-header-row-height: 30px;
}Colors
| Variable | Description |
|---|---|
--ut-accent-color | selection, focus, sort icons, active states |
--ut-accent-contrast-color | text on accent colored backgrounds |
--ut-focus-color | outline of the focused cell |
--ut-color / --ut-muted-color | text colors |
--ut-background | background of the grid |
--ut-header-background / --ut-header-color | header, filter row, group area, pagination |
--ut-border-color | outer border, header borders |
--ut-row-border-color | lines between rows |
--ut-row-background / --ut-row-alt-background | even / odd rows |
--ut-row-hover-background | hovered row |
--ut-selection-background / --ut-selection-hover-background | selected rows |
--ut-range-selection-background | selected cells |
--ut-group-background | group rows |
--ut-total-background | pinned and total rows |
--ut-icon-color | icons |
--ut-input-background / --ut-input-border-color | inputs of filters and editors |
--ut-popup-background / --ut-popup-shadow | menus and filter popups |
--ut-pinned-shadow | shadow of pinned columns |
Sizes and typography
| Variable | Default | Description |
|---|---|---|
--ut-font-family | inherit | |
--ut-font-size | 14px | |
--ut-line-height | 20px | |
--ut-header-font-weight | 600 | |
--ut-cell-padding-x / --ut-cell-padding-y | 8px / 6px | padding of cells (defines the row height if rows are measured) |
--ut-header-row-height | 38px | height of header rows (also grid option headerRowHeight) |
--ut-filter-row-height | 36px | height of the filter row (also grid option filterRowHeight) |
--ut-radius | 6px | border radius of the grid |
--ut-icon-size | 14px | |
--ut-cell-border | none | vertical cell borders, e.g. 1px solid var(--ut-row-border-color) |
Density
Switch the density with a class that sets the size variables. A compact grid shows more rows for data-heavy screens (spreadsheets, logs), a comfortable grid is easier to read and to use on touch devices. Row heights follow automatically because rows are measured.
Source
vue
<template>
<div class="demo">
<div class="demo-toolbar">
<label v-for="value in densities" :key="value">
<input v-model="density" type="radio" :value="value" /> {{ value }}
</label>
</div>
<Datagrid :class="`sd-${density}`" :columns="columns" :rows="rows" :options="options" />
</div>
</template>
<script setup lang="ts">
import { ref } from "vue";
import { Datagrid, type ColumnConfig, type GridOptions } from "@datagrid/vue-ui";
import { createEmployees, type Employee } from "./data";
const densities = ["compact", "normal", "comfortable"];
const density = ref("compact");
const rows = createEmployees(200);
const columns: Array<ColumnConfig<Employee>> = [
{ field: "firstName", text: "First name" },
{ field: "lastName", text: "Last name" },
{ field: "department", text: "Department" },
{ field: "city", text: "City" },
{ field: "salary", text: "Salary", type: "number" },
];
const options: GridOptions<Employee> = {
checkboxSelection: true,
defaultColumn: { sortable: true, filterable: true, flex: 1 },
};
</script>
<style>
/* the row height follows the padding and line height, because rows are measured */
.uni-table.sd-compact {
--ut-font-size: 12px;
--ut-line-height: 16px;
--ut-cell-padding-y: 2px;
--ut-cell-padding-x: 6px;
--ut-header-row-height: 28px;
--ut-filter-row-height: 30px;
}
.uni-table.sd-comfortable {
--ut-font-size: 15px;
--ut-line-height: 22px;
--ut-cell-padding-y: 12px;
--ut-cell-padding-x: 14px;
--ut-header-row-height: 48px;
--ut-filter-row-height: 44px;
}
</style>tsx
import { useState } from "react";
import { Datagrid, type ColumnConfig, type GridOptions } from "@datagrid/react-ui";
import { createEmployees, type Employee } from "../data";
const densities = ["compact", "normal", "comfortable"];
const rows = createEmployees(200);
const columns: Array<ColumnConfig<Employee>> = [
{ field: "firstName", text: "First name" },
{ field: "lastName", text: "Last name" },
{ field: "department", text: "Department" },
{ field: "city", text: "City" },
{ field: "salary", text: "Salary", type: "number" },
];
const options: GridOptions<Employee> = {
checkboxSelection: true,
defaultColumn: { sortable: true, filterable: true, flex: 1 },
};
export default function StylingDensity() {
const [density, setDensity] = useState("compact");
return (
<div className="demo">
<div className="demo-toolbar">
{densities.map((value) => (
<label key={value}>
<input type="radio" checked={density === value} onChange={() => setDensity(value)} /> {value}
</label>
))}
</div>
<Datagrid className={`sd-${density}`} columns={columns} rows={rows} options={options} />
<style>{`
/* the row height follows the padding and line height, because rows are measured */
.uni-table.sd-compact {
--ut-font-size: 12px;
--ut-line-height: 16px;
--ut-cell-padding-y: 2px;
--ut-cell-padding-x: 6px;
--ut-header-row-height: 28px;
--ut-filter-row-height: 30px;
}
.uni-table.sd-comfortable {
--ut-font-size: 15px;
--ut-line-height: 22px;
--ut-cell-padding-y: 12px;
--ut-cell-padding-x: 14px;
--ut-header-row-height: 48px;
--ut-filter-row-height: 44px;
}
`}</style>
</div>
);
}Custom themes
A brand theme is a set of variables on a class. Start with the colors you need – usually --ut-accent-color, the header colors and the row backgrounds – and add sizes, fonts or vertical cell borders (--ut-cell-border). A dark brand theme uses theme="dark" as base and overrides only a few colors.
Source
vue
<template>
<div class="demo">
<div class="demo-toolbar">
Theme
<select v-model="brand">
<option value="ocean">Ocean</option>
<option value="sunset">Sunset</option>
<option value="midnight">Midnight (dark)</option>
</select>
</div>
<!-- a dark brand theme builds on the dark theme and only changes a few colors -->
<Datagrid
:class="`sb-${brand}`"
:theme="brand === 'midnight' ? 'dark' : 'light'"
:columns="columns"
:rows="rows"
:options="options"
/>
</div>
</template>
<script setup lang="ts">
import { ref } from "vue";
import { Datagrid, formatters, type ColumnConfig, type GridOptions } from "@datagrid/vue-ui";
import { createEmployees, type Employee } from "./data";
const brand = ref("ocean");
const rows = createEmployees(200);
const columns: Array<ColumnConfig<Employee>> = [
{ field: "firstName", text: "First name" },
{ field: "lastName", text: "Last name" },
{ field: "department", text: "Department" },
{ field: "salary", text: "Salary", type: "number", aggFunc: "sum", valueFormatter: formatters.number() },
{ field: "remote", text: "Remote", type: "boolean" },
];
const options: GridOptions<Employee> = {
checkboxSelection: true,
totalRow: "bottom",
columnMenu: true,
defaultColumn: { sortable: true, filterable: true, flex: 1 },
};
</script>
<style>
.uni-table.sb-ocean {
--ut-accent-color: #0550ae;
--ut-header-background: #0969da;
--ut-header-color: #ffffff;
--ut-icon-color: #0550ae;
--ut-row-alt-background: #f3f8ff;
--ut-selection-background: #ddf4ff;
--ut-cell-border: 1px solid var(--ut-row-border-color);
--ut-radius: 0;
}
/* icons in the colored header */
.uni-table.sb-ocean .ut-header {
--ut-icon-color: #ffffff;
}
.uni-table.sb-sunset {
--ut-accent-color: #d1480b;
--ut-header-background: #fff4ec;
--ut-header-color: #8a2d05;
--ut-row-border-color: #fbe3d3;
--ut-row-alt-background: #fffaf6;
--ut-selection-background: #ffe6d5;
--ut-total-background: #fff4ec;
--ut-font-family: Georgia, serif;
--ut-font-size: 13px;
--ut-radius: 14px;
}
.uni-table.sb-midnight {
--ut-accent-color: #a371f7;
--ut-background: #0d1021;
--ut-header-background: #161a33;
--ut-row-alt-background: #11152a;
--ut-row-border-color: #242a4a;
--ut-selection-background: #2c2356;
--ut-total-background: #161a33;
}
</style>tsx
import { useState } from "react";
import { Datagrid, formatters, type ColumnConfig, type GridOptions } from "@datagrid/react-ui";
import { createEmployees, type Employee } from "../data";
const rows = createEmployees(200);
const columns: Array<ColumnConfig<Employee>> = [
{ field: "firstName", text: "First name" },
{ field: "lastName", text: "Last name" },
{ field: "department", text: "Department" },
{ field: "salary", text: "Salary", type: "number", aggFunc: "sum", valueFormatter: formatters.number() },
{ field: "remote", text: "Remote", type: "boolean" },
];
const options: GridOptions<Employee> = {
checkboxSelection: true,
totalRow: "bottom",
columnMenu: true,
defaultColumn: { sortable: true, filterable: true, flex: 1 },
};
export default function StylingBrand() {
const [brand, setBrand] = useState("ocean");
return (
<div className="demo">
<div className="demo-toolbar">
Theme
<select value={brand} onChange={(e) => setBrand(e.target.value)}>
<option value="ocean">Ocean</option>
<option value="sunset">Sunset</option>
<option value="midnight">Midnight (dark)</option>
</select>
</div>
{/* a dark brand theme builds on the dark theme and only changes a few colors */}
<Datagrid
className={`sb-${brand}`}
theme={brand === "midnight" ? "dark" : "light"}
columns={columns}
rows={rows}
options={options}
/>
<style>{`
.uni-table.sb-ocean {
--ut-accent-color: #0550ae;
--ut-header-background: #0969da;
--ut-header-color: #ffffff;
--ut-icon-color: #0550ae;
--ut-row-alt-background: #f3f8ff;
--ut-selection-background: #ddf4ff;
--ut-cell-border: 1px solid var(--ut-row-border-color);
--ut-radius: 0;
}
/* icons in the colored header */
.uni-table.sb-ocean .ut-header {
--ut-icon-color: #ffffff;
}
.uni-table.sb-sunset {
--ut-accent-color: #d1480b;
--ut-header-background: #fff4ec;
--ut-header-color: #8a2d05;
--ut-row-border-color: #fbe3d3;
--ut-row-alt-background: #fffaf6;
--ut-selection-background: #ffe6d5;
--ut-total-background: #fff4ec;
--ut-font-family: Georgia, serif;
--ut-font-size: 13px;
--ut-radius: 14px;
}
.uni-table.sb-midnight {
--ut-accent-color: #a371f7;
--ut-background: #0d1021;
--ut-header-background: #161a33;
--ut-row-alt-background: #11152a;
--ut-row-border-color: #242a4a;
--ut-selection-background: #2c2356;
--ut-total-background: #161a33;
}
`}</style>
</div>
);
}Row height
Without rowHeight every row is measured, so rows can have different heights (multi line text with autoHeight, custom components). With a fixed rowHeight (e.g. 32) no measuring is necessary, which is faster for very large grids.
CSS classes
The most important classes to write your own styles:
| Class | Element |
|---|---|
.uni-table | root element |
.ut-header-cell | header cell (.ut-sorted, .ut-filtered, .ut-pinned-left, …) |
.ut-filter-cell | cell of the filter row |
.ut-row | row (.odd, .even, .selected, .hovered, .focused, .ut-row-pinned, .ut-row-total) |
.ut-group | group row |
.ut-cell | cell (.selected, .focused, .ut-editable, .ut-pinned-left, .left / .center / .right) |
.ut-editing | editor |
.ut-pagination | pagination bar |
.ut-popup | menus and filter popups |
Dark mode of your application
If your application switches the color scheme with a class (e.g. html.dark), set the variables for this class or use :theme="isDark ? 'dark' : 'light'".
Conditional formatting
Row classes
rowClass (and rowStyle) style complete rows depending on the data – overdue tasks, archived records, rows with errors. rowClass returns a class name or an array of class names. Set row colors with the variables --ut-row-background / --ut-row-alt-background on the row class: hover and selection keep working.
Source
vue
<template>
<div class="demo">
<div class="demo-toolbar">
<span class="sr-legend sr-overdue">overdue</span>
<span class="sr-legend sr-done">done</span>
<span class="sr-legend sr-high">high priority</span>
<span>(today is {{ TODAY }})</span>
</div>
<Datagrid :columns="columns" :rows="rows" :options="options" />
</div>
</template>
<script setup lang="ts">
import { Datagrid, formatters, type ColumnConfig, type GridOptions } from "@datagrid/vue-ui";
import { createTasks, TODAY, type Task } from "./data";
const rows = createTasks(100);
const isOverdue = (task: Task) => !task.done && task.due < TODAY;
const columns: Array<ColumnConfig<Task>> = [
{ field: "title", text: "Task", flex: 1 },
{ field: "assignee", text: "Assignee", width: 150 },
{ field: "status", text: "Status", width: 110 },
{ field: "priority", text: "Priority", width: 90 },
{
field: "due",
text: "Due",
type: "date",
width: 150,
valueFormatter: formatters.date({ dateStyle: "medium" }),
// cell classes are added to the row classes
cellClass: ({ data }) => (isOverdue(data) ? "sr-due-overdue" : undefined),
},
];
const options: GridOptions<Task> = {
// one or more classes per row, depending on the data
rowClass: (row) => [
isOverdue(row.data) ? "sr-overdue" : "",
row.data.done ? "sr-done" : "",
],
// inline styles work as well
rowStyle: (row) => (row.data.priority === "high" ? { fontWeight: "600" } : undefined),
defaultColumn: { sortable: true },
};
</script>
<style>
.uni-table .sr-overdue {
--ut-row-background: color-mix(in srgb, #cf222e 10%, var(--ut-background));
--ut-row-alt-background: var(--ut-row-background);
color: #cf222e;
}
.uni-table .sr-done {
color: var(--ut-muted-color);
text-decoration: line-through;
}
.sr-due-overdue::after {
content: "⚠";
margin-left: 6px;
}
.sr-legend {
padding: 1px 8px;
border-radius: 4px;
}
.sr-legend.sr-overdue {
background: color-mix(in srgb, #cf222e 12%, transparent);
color: #cf222e;
}
.sr-legend.sr-done {
color: var(--vp-c-text-2);
text-decoration: line-through;
}
.sr-legend.sr-high {
font-weight: 600;
}
</style>tsx
import { Datagrid, formatters, type ColumnConfig, type GridOptions } from "@datagrid/react-ui";
import { createTasks, TODAY, type Task } from "../data";
const rows = createTasks(100);
const isOverdue = (task: Task) => !task.done && task.due < TODAY;
const columns: Array<ColumnConfig<Task>> = [
{ field: "title", text: "Task", flex: 1 },
{ field: "assignee", text: "Assignee", width: 150 },
{ field: "status", text: "Status", width: 110 },
{ field: "priority", text: "Priority", width: 90 },
{
field: "due",
text: "Due",
type: "date",
width: 150,
valueFormatter: formatters.date({ dateStyle: "medium" }),
// cell classes are added to the row classes
cellClass: ({ data }) => (isOverdue(data) ? "sr-due-overdue" : undefined),
},
];
const options: GridOptions<Task> = {
// one or more classes per row, depending on the data
rowClass: (row) => [
isOverdue(row.data) ? "sr-overdue" : "",
row.data.done ? "sr-done" : "",
],
// inline styles work as well
rowStyle: (row) => (row.data.priority === "high" ? { fontWeight: "600" } : undefined),
defaultColumn: { sortable: true },
};
export default function StylingRowClass() {
return (
<div className="demo">
<div className="demo-toolbar">
<span className="sr-legend sr-overdue">overdue</span>
<span className="sr-legend sr-done">done</span>
<span className="sr-legend sr-high">high priority</span>
<span>(today is {TODAY})</span>
</div>
<Datagrid columns={columns} rows={rows} options={options} />
<style>{`
.uni-table .sr-overdue {
--ut-row-background: color-mix(in srgb, #cf222e 10%, var(--ut-background));
--ut-row-alt-background: var(--ut-row-background);
color: #cf222e;
}
.uni-table .sr-done {
color: var(--ut-muted-color);
text-decoration: line-through;
}
.sr-due-overdue::after {
content: "⚠";
margin-left: 6px;
}
.sr-legend {
padding: 1px 8px;
border-radius: 4px;
}
.sr-legend.sr-overdue {
background: color-mix(in srgb, #cf222e 12%, transparent);
color: #cf222e;
}
.sr-legend.sr-done {
color: var(--vp-c-text-2);
text-decoration: line-through;
}
.sr-legend.sr-high {
font-weight: 600;
}
`}</style>
</div>
);
}Results of rowClass, rowStyle, cellClass and cellStyle are cached and recalculated when the data of the row changes.
Specificity
The grid styles cells with selectors like .uni-table .ut-cell. Prefix your own rules with .uni-table (e.g. .uni-table .overdue) if they set properties the grid sets as well, like background-color.
Cell styles and heatmaps
cellClass and cellStyle style single cells by their value. Classes are best for a few states (positive / negative), cellStyle for continuous values like the background of a heatmap. The demo mixes the color with --ut-current-row-background, the background of the row including hover and selection.
Source
vue
<template>
<div class="demo">
<div class="demo-toolbar">
<label><input v-model="heatmap" type="checkbox" /> Heatmap</label>
<span class="sh-scale">low <span class="sh-scale__bar"></span> high</span>
</div>
<Datagrid :columns="columns" :rows="rows" :options="options" />
</div>
</template>
<script setup lang="ts">
import { computed, ref } from "vue";
import { Datagrid, type ColumnConfig, type GridOptions } from "@datagrid/vue-ui";
import { createRandom } from "./data";
type Sales = { region: string; growth: number } & Record<string, number>;
const months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
const regions = ["North", "East", "South", "West", "Central", "Alps", "Coast", "Islands"];
const random = createRandom(3);
const rows = regions.map((region) => {
const row = { region, growth: Math.round((random() - 0.4) * 300) / 10 } as Sales;
months.forEach((month, i) => (row[month] = Math.round(40 + random() * 60 + Math.sin(i / 2) * 25)));
return row;
});
const heatmap = ref(true);
// value between min and max -> background with 0-60 % of the accent color
const values = rows.flatMap((row) => months.map((month) => row[month]));
const min = Math.min(...values);
const max = Math.max(...values);
const heat = (value: number) => Math.round(((value - min) / (max - min)) * 60);
const columns = computed<Array<ColumnConfig<Sales>>>(() => [
{ field: "region", text: "Region", width: 100, pinned: "left" },
...months.map((month) => ({
field: month,
text: month,
type: "number",
width: 62,
cellStyle: heatmap.value
? ({ value }: { value: number }) => ({
backgroundColor: `color-mix(in srgb, #2e9e6e ${heat(value)}%, var(--ut-current-row-background))`,
})
: undefined,
})),
{
field: "growth",
text: "Growth",
type: "number",
width: 90,
pinned: "right",
valueFormatter: ({ value }) => `${value > 0 ? "▲" : "▼"} ${Math.abs(value)} %`,
// classes for conditional formatting, styled with css
cellClass: ({ value }) => (value > 10 ? "sh-strong" : value < 0 ? "sh-negative" : "sh-positive"),
},
]);
const options: GridOptions<Sales> = {
defaultColumn: { sortable: true },
};
</script>
<style>
.uni-table .sh-positive {
color: #1a7f37;
}
.uni-table .sh-strong {
color: #1a7f37;
font-weight: 700;
}
.uni-table .sh-negative {
color: #cf222e;
}
.sh-scale {
display: inline-flex;
align-items: center;
gap: 6px;
font-size: 12px;
}
.sh-scale__bar {
width: 80px;
height: 10px;
border-radius: 2px;
background: linear-gradient(to right, transparent, color-mix(in srgb, #2e9e6e 60%, transparent));
border: 1px solid var(--vp-c-divider);
}
</style>tsx
import { useMemo, useState } from "react";
import { Datagrid, type ColumnConfig, type GridOptions } from "@datagrid/react-ui";
import { createRandom } from "../data";
type Sales = { region: string; growth: number } & Record<string, number>;
const months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
const regions = ["North", "East", "South", "West", "Central", "Alps", "Coast", "Islands"];
const random = createRandom(3);
const rows = regions.map((region) => {
const row = { region, growth: Math.round((random() - 0.4) * 300) / 10 } as Sales;
months.forEach((month, i) => (row[month] = Math.round(40 + random() * 60 + Math.sin(i / 2) * 25)));
return row;
});
// value between min and max -> background with 0-60 % of the accent color
const values = rows.flatMap((row) => months.map((month) => row[month]));
const min = Math.min(...values);
const max = Math.max(...values);
const heat = (value: number) => Math.round(((value - min) / (max - min)) * 60);
function createColumns(heatmap: boolean): Array<ColumnConfig<Sales>> {
return [
{ field: "region", text: "Region", width: 100, pinned: "left" },
...months.map((month) => ({
field: month,
text: month,
type: "number",
width: 62,
cellStyle: heatmap
? ({ value }: { value: number }) => ({
backgroundColor: `color-mix(in srgb, #2e9e6e ${heat(value)}%, var(--ut-current-row-background))`,
})
: undefined,
})),
{
field: "growth",
text: "Growth",
type: "number",
width: 90,
pinned: "right",
valueFormatter: ({ value }) => `${value > 0 ? "▲" : "▼"} ${Math.abs(value)} %`,
// classes for conditional formatting, styled with css
cellClass: ({ value }) => (value > 10 ? "sh-strong" : value < 0 ? "sh-negative" : "sh-positive"),
},
];
}
const options: GridOptions<Sales> = {
defaultColumn: { sortable: true },
};
export default function StylingHeatmap() {
const [heatmap, setHeatmap] = useState(true);
const columns = useMemo(() => createColumns(heatmap), [heatmap]);
return (
<div className="demo">
<div className="demo-toolbar">
<label>
<input type="checkbox" checked={heatmap} onChange={(e) => setHeatmap(e.target.checked)} /> Heatmap
</label>
<span className="sh-scale">
low <span className="sh-scale__bar"></span> high
</span>
</div>
<Datagrid columns={columns} rows={rows} options={options} />
<style>{`
.uni-table .sh-positive {
color: #1a7f37;
}
.uni-table .sh-strong {
color: #1a7f37;
font-weight: 700;
}
.uni-table .sh-negative {
color: #cf222e;
}
.sh-scale {
display: inline-flex;
align-items: center;
gap: 6px;
font-size: 12px;
}
.sh-scale__bar {
width: 80px;
height: 10px;
border-radius: 2px;
background: linear-gradient(to right, transparent, color-mix(in srgb, #2e9e6e 60%, transparent));
border: 1px solid var(--vp-c-divider);
}
`}</style>
</div>
);
}SCSS
The source of the styles is available as @datagrid/core/style.scss if you want to include it in your own SCSS build.