Skip to content

Style Unovis Charts ​

VUEDA ships an optional stylesheet, unovis.css, that gives Unovis charts VUEDA's typography, surfaces, and dark mode. You build charts with the Unovis Vue components. The stylesheet changes only how they look.

Install and activate ​

The stylesheet targets @unovis/ts and @unovis/vue 1.7.0. Install both in the application that draws charts:

sh
pnpm add @unovis/ts@1.7.0 @unovis/vue@1.7.0

Set up the client as described in Client Plugin Prerequisites, including the vueda-tailwind/base.css import. Then import the chart stylesheet from the chart component or the chart route:

js
import "@vueda/theme/vueda-tailwind/unovis.css";

Put class="unovis-vueda" on an element that wraps the chart, its legend, and its tooltip. VUEDA's startup imports load neither this stylesheet nor Unovis, so lazy-load the chart component to keep chart code out of other routes.

Working example ​

Hover a bar or a line to see its tooltip. Switch the documentation site's appearance setting while the charts are on screen to see both modes. The wrapper of the second chart points the first palette slot at slot 5. Its North series changes color, and the first chart keeps the default blue. The table below the charts gives the same data without relying on a pointer or on color.

The complete Vue source:

vue
<script setup>
import { CurveType, StackedBar } from "@unovis/ts";
import {
    VisAxis,
    VisBulletLegend,
    VisCrosshair,
    VisLine,
    VisStackedBar,
    VisTooltip,
    VisXYContainer,
} from "@unovis/vue";
import "@vueda/theme/vueda-tailwind/unovis.css";

const data = [
    { week: 1, north: 12, south: 7 },
    { week: 2, north: 9, south: 11 },
    { week: 3, north: 15, south: 8 },
    { week: 4, north: 18, south: 12 },
];
const x = (row) => row.week;
const y = [(row) => row.north, (row) => row.south];
const colors = ["var(--vueda-chart-1)", "var(--vueda-chart-2)"];
const color = (_, index) => colors[index];
const dash = (_, index) => (index === 0 ? [] : [7, 3]);
const items = [
    { name: "North", color: colors[0] },
    { name: "South", color: colors[1] },
];
function tooltip(row) {
    const node = document.createElement("div");
    node.style.fontSize = "var(--vueda-text-supporting)";
    node.textContent = `Week ${row.week}: North ${row.north}, South ${row.south}`;
    return node;
}
const barTriggers = { [StackedBar.selectors.bar]: (bar) => tooltip(bar.datum) };
</script>

<template>
    <div class="flex flex-col gap-6">
        <div class="unovis-vueda bg-card text-card-foreground rounded border border-solid border-[var(--border)] p-4">
            <p class="mb-2 text-sm font-medium">Default chart colors</p>
            <VisXYContainer :data="data" :height="200" :y-domain="[0, undefined]">
                <VisStackedBar :x="x" :y="y" :color="color" :bar-padding="0.4" />
                <VisAxis type="x" :tick-values="[1, 2, 3, 4]" :grid-line="false" />
                <VisAxis type="y" :num-ticks="4" :domain-line="false" />
                <VisTooltip :triggers="barTriggers" />
            </VisXYContainer>
            <VisBulletLegend :items="items" />
        </div>
        <div
            class="unovis-vueda bg-card text-card-foreground rounded border border-solid border-[var(--border)] p-4"
            style="--vueda-chart-1: var(--vueda-chart-5)"
        >
            <p class="mb-2 text-sm font-medium">Local override: North uses palette slot 5</p>
            <VisXYContainer :data="data" :height="200" :y-domain="[0, undefined]">
                <VisLine :x="x" :y="y" :color="color" :line-dash-array="dash" :curve-type="CurveType.Linear" />
                <VisAxis type="x" :tick-values="[1, 2, 3, 4]" :grid-line="false" />
                <VisAxis type="y" :num-ticks="4" :domain-line="false" />
                <VisCrosshair :x="x" :y="y" :color="color" :template="tooltip" />
                <VisTooltip />
            </VisXYContainer>
            <VisBulletLegend :items="items" />
        </div>
        <table>
            <caption>
                Order counts used by both charts
            </caption>
            <thead>
                <tr>
                    <th scope="col">Week</th>
                    <th scope="col">North (solid line)</th>
                    <th scope="col">South (dashed line)</th>
                </tr>
            </thead>
            <tbody>
                <tr v-for="row in data" :key="row.week">
                    <th scope="row">{{ row.week }}</th>
                    <td>{{ row.north }}</td>
                    <td>{{ row.south }}</td>
                </tr>
            </tbody>
        </table>
    </div>
</template>

The chart palette ​

The five tokens --vueda-chart-1 to --vueda-chart-5 hold a palette derived from the Okabe-Ito colors (Masataka Okabe and Kei Ito, Color Universal Design). The comment at the top of unovis.css records how each color was derived. Against the default card background, every light-mode color has a contrast ratio of at least 3.18:1, and every dark-mode color has at least 6.05:1. Contrast against the surface does not make two series easy to tell apart. Label each series, give lines different dash patterns, and offer the data as a table. Check contrast again when you change the palette or the card surface.

The palette marks which category a series belongs to. It is separate from the status tokens --primary, --success, --warning, and --destructive. When a chart shows status, assign those tokens to the series yourself and label what each color means.

Override the palette ​

The palette tokens are CSS custom properties. Theming and Customization describes how VUEDA's CSS tokens and their overrides work. This section covers the load order that applies to unovis.css.

unovis.css declares the palette tokens on :root and .dark. In a production build, Vite puts a stylesheet that a lazily loaded chart component imports into the CSS file of the component's chunk. The browser adds the chunk's CSS file after the application's own stylesheets when the chunk loads. A :root rule in the application's main stylesheet therefore loses to the palette in unovis.css. The two rules have the same specificity, so the later one wins.

To change the palette for every chart, use one of these two placements:

  • On the chart wrapper class. unovis.css declares no palette tokens on .unovis-vueda, so a rule on that class applies from any stylesheet, whatever the load order:

    css
    .unovis-vueda {
        --vueda-chart-1: oklch(0.55 0.13 250);
    }
    .dark .unovis-vueda {
        --vueda-chart-1: oklch(0.75 0.13 250);
    }
  • In a stylesheet that the chart component imports after unovis.css. Vite keeps the two files in import order inside the chunk's CSS file, so :root and .dark rules in the second file win:

    js
    import "./chart-palette.css";
    import "@vueda/theme/vueda-tailwind/unovis.css";

To change one chart, set the token on that chart's wrapper element, as the second chart in the example does. Pointing a slot at another palette token, such as var(--vueda-chart-5), keeps the light and dark values of that token.

The optional --vueda-chart-default token sets the color of single-series charts. When it is unset, --vis-color-main uses --vueda-chart-1.

unovis.css declares the Unovis variables in the mapping reference on .unovis-vueda itself. To change one of them, set it in a stylesheet that the chart component imports after unovis.css, or use a more specific selector, such as .unovis-vueda.sales-chart.

Assign colors to series ​

For a chart with more than one series, pass palette tokens as CSS strings, such as "var(--vueda-chart-2)", to the Unovis color accessor, the legend items, and the crosshair colors. Assign each series its token and dash pattern by a stable key, such as the series name. When a filter hides some series, look up each remaining series by its key so that it keeps its color. The example uses two fixed series and assigns colors by position, which is safe only because its series never change. The palette has five slots, so a chart with more than five categories needs labels or patterns to tell the extra ones apart.

Dark mode and tooltip scope ​

The mapping reads VUEDA tokens that change under the .dark class, so charts and tooltips that are already on screen change with the page's mode. Unovis also has its own dark theme, which it applies under classes such as theme-dark. Do not enable it for these charts.

Unovis places a tooltip inside the chart container by default. The tooltip is then inside the .unovis-vueda wrapper and uses the mapping. A tooltip given container: document.body is outside the wrapper and loses the mapping. If a tooltip must render elsewhere, give it a host element with the unovis-vueda class, inside the element that carries .dark. Repeat any chart-level token overrides on that host, because the host does not inherit them from the chart.

Mapping reference ​

unovis.css sets these Unovis variables on .unovis-vueda:

Unovis variablesVUEDA token or fixed value
--vis-color-main--vueda-chart-default; --vueda-chart-1 when that is unset
--vis-color0 to --vis-color4--vueda-chart-1 to --vueda-chart-5
--vis-font-family, --vis-axis-font-family, --vis-legend-font-family--vueda-font-sans
--vis-axis-tick-label-font-size, --vis-axis-label-font-size, --vis-legend-label-font-size--vueda-text-supporting
--vis-axis-tick-label-color, --vis-axis-label-color, --vis-crosshair-line-stroke-color--muted-foreground
--vis-axis-tick-color, --vis-axis-domain-color, --vis-axis-grid-color--border
--vis-axis-grid-line-width, --vis-axis-domain-line-width, --vis-axis-tick-line-width1px
--vis-axis-grid-line-dasharray, --vis-axis-domain-line-dasharraynone
--vis-legend-label-color--foreground
--vis-legend-item-spacing, --vis-legend-vertical-item-spacing16px, 8px
--vis-tooltip-background-color, --vis-tooltip-text-color--popover, --popover-foreground
--vis-tooltip-border-color, --vis-tooltip-border-radius--border, --vueda-card-radius
--vis-tooltip-box-shadow, --vis-tooltip-transition-duration--vueda-shadow-popover, --vueda-duration-interaction
--vis-tooltip-padding, --vis-tooltip-backdrop-filter0.375rem 0.5rem, none

Unovis 1.7.0 has no variable for tooltip font size. The example sets the font size on the element that its tooltip function returns. It fills that element with textContent so that data values are never parsed as HTML.

Coverage and limits ​

The stylesheet covers the components in the example: VisStackedBar, VisLine, VisAxis, VisBulletLegend, VisCrosshair, and VisTooltip. It sets their palette, typography, axis lines, legend text and spacing, crosshair line color, and tooltip surface. Other Unovis components keep their Unovis defaults. Chart geometry, line patterns, data accessors, and behavior are Unovis props. Unovis theming lists every variable that Unovis reads.

The example renders fixed data. For examples that load chart data from the API, see issue 321.