Skip to content
silverpoint
Documentation menu

New in 0.7

Palettes

New in silverpoint 0.7 (React, Vue, Angular) and reflex-silverpoint-react 0.7. Re-colour every chart, UI component, dashboard and composed component with six presets named after drawing media, or a palette of your own, checked against silverpoint's contrast thresholds.

Colour without re-rendering

silverpoint never writes a colour into its drawing: every stroke and fill is painted by CSS through the ground's --sp- custom properties. A palette sets them, so it re-colours a chart, a UI component, a dashboard, a composed component or the timeline without re-rendering it. palette= takes a preset name, a Palette, a dict of fields or a state var holding one.

tsx
import { sanguine } from '@silverpoint/core/palettes'; // npm install @silverpoint/core
import { SilverpointProvider } from '@silverpoint/react';
import { BarChart } from '@silverpoint/react/bar-chart';
import { SpButton } from '@silverpoint/react/ui/button';

<SilverpointProvider palette={sanguine}> {/* a whole section */}
  <BarChart title="Orders" />
  <SpButton variant="primary" palette={{ ink: '#2C6355' }}>Save</SpButton> {/* one field changed */}
</SilverpointProvider>

The 6 presets

Named after historical drawing media, each verified by check_palette against the contrast thresholds of the data model.

silverpointpasses

The silverpoint ground's own inks on cream: what a chart draws with no palette.

HitsHits
Hits
hourhits
0018
0411
0842
1264
1657
2030

ink on substrate 5.29:1

sanguinepasses

Red chalk, as in Leonardo's and Andrea del Sarto's studies.

HitsHits
Hits
hourhits
0018
0411
0842
1264
1657
2030

ink on substrate 6.40:1

sepiapasses

Sepia wash and bistre.

HitsHits
Hits
hourhits
0018
0411
0842
1264
1657
2030

ink on substrate 6.29:1

iron_gallpasses

Iron-gall ink: the blue-black of manuscripts.

HitsHits
Hits
hourhits
0018
0411
0842
1264
1657
2030

ink on substrate 7.73:1

verdigrispasses

Verdigris, the copper green of illuminators.

HitsHits
Hits
hourhits
0018
0411
0842
1264
1657
2030

ink on substrate 5.84:1

indigopasses

Indigo, the dyers' and printers' blue.

HitsHits
Hits
hourhits
0018
0411
0842
1264
1657
2030

ink on substrate 7.17:1

A whole section: silverpoint_palette

silverpoint_palette(*children, palette=...) colours every chart, UI component, dashboard and surface inside it. The wrapper is display: contents, so it takes no layout space, and an element's own palette always wins over its section's. Pick a preset:

Palette
RevenueRevenue
Revenue
pointvalue
1118
2124
3121
4132
5140
6136
7129
8142
9151
10147
11155
12162
13158
14171
Units soldUnits sold
Units sold
monthunits
Jan120
Feb98
Mar143

Release

python
class Theme(rx.State):
    preset: str = "sanguine"

    @rx.var
    def palette(self) -> dict[str, str]:
        return PALETTES[self.preset].to_dict()

    @rx.event
    def set_preset(self, name: str):
        self.preset = name


sp_segmented(items=[ui_item(name, name) for name in PALETTES], value=Theme.preset, on_change=Theme.set_preset)
silverpoint_palette(dashboard(...), palette=Theme.palette)  # re-colours without re-rendering

Build your own

Unset fields keep the ground's own colour. Pick the five that matter most; the preview reads them as a dict, as a state var would. Then measure the result with check_palette: it returns one message per field below its threshold, and [] when the palette passes (only hex colours can be measured; a palette without substrate is measured against cream, #EDE7DA). In JavaScript, checkPalette and contrastRatio on @silverpoint/core/palettes do the same:

Units soldUnits sold
Units sold
monthunits
Jan120
Feb98
Mar143

A card

7
Palette(
    substrate="#F2E8DC",
    ink="#8A3A24",
    ink_secondary="#6A4A3A",
    text="#4A2216",
    text_muted="#7A3E2C",
)
ts
import { checkPalette, contrastRatio, sanguine } from '@silverpoint/core/palettes';

const mine = { ...sanguine, ink: '#5A1F14' }; // fields are camelCase: inkSecondary, textMuted
checkPalette(mine); // [] when every pair meets its threshold, else { pair, ratio, threshold, … }
contrastRatio('#5A1F14', '#F2E8DC'); // the WCAG ratio, 1 to 21

Fields

FieldCustom propertyMinimum contrast on the substrate
substrate--sp-substrate— (what the others are measured on)
ink--sp-ink4.5:1
ink_secondary--sp-ink-secondary4.5:1
heighten--sp-heightenexempt (always outlined)
rule--sp-rule3.0:1
grid--sp-grid3.0:1
text--sp-text4.5:1
text_muted--sp-text-muted4.5:1
focus--sp-ui-focus-color3.0:1

Palette.to_dict(), to_css() and merged(**changes) convert and copy palettes; Palette.from_dict() rejects unknown fields. palette_css(palette) returns the declarations as a style dict, and contrast_ratio(fg, bg) is the WCAG ratio of two hex colours.

Rules

  • Monochrome by design. Tone is built from hatch density, so a chart has two inks — ink for the main series and ink_secondary for the second — never a colour per series.
  • precision mode and the system's high-contrast modes still apply on top of any palette.
  • React, Vue and Angular: SilverpointProvider / provideSilverpoint take a palette; a nested provider, a dashboard cell or a palette prop overrides it field by field. A value is accepted only if it is a CSS colour.
  • Reflex: silverpoint_provider takes a palette, and silverpoint_palette is a provider with one. Overlays render in place and read it; only sp_trigger does not, so give it palette directly.
Note
The names differ by language: ironGall, inkSecondary and textMuted in JavaScript are iron_gall, ink_secondary and text_muted in Python. Both set the same --sp- custom properties, which you can also set in CSS (the Tailwind preset names them as tokens).