Skip to content
silverpoint
Documentation menu

Concepts

UI components

Since 0.3. 17 interface components drawn as the charts are: frames hand-drawn by each ground's own inker, tone laid as hatching (silverpoint) or as the weight of an exact white line (cyanotype), and a native control underneath every one. The controls on this site are these components. 0.7 adds 21 more — composed components and overlays — documented on Composed components.

Try it

Every control here is a silverpoint component bound to client-side state: the segmented control re-grounds the panel, the switch turns the inking off on the components and the charts alike, the slider drives the gauge and the progress bar.

Ground
How does it look?
TrafficTraffic
Traffic
hourhits
0018
0214
0411
0616
0834
1058
1271
1466
1674
1888
2062
2241
rating4inksilverpoint
Load is fine
Move the slider above 85.

The catalog

GroupComponents
ActionsButtonA native button, or a link drawn as one.
Data entryInput, Checkbox, RadioGroup, Switch, Slider, Rate, SegmentedNative inputs, checkboxes, radios and ranges in hand-drawn frames: forms submit natively.
NavigationTabs, StepsTabs with their panels and an ordered list of steps, with the WAI-ARIA keyboard patterns.
Data displayCard, Tag, Badge, DividerCards, toned tags, counting badges and captioned rules.
FeedbackProgress, Alert, SkeletonProgress as a line or a circle, alerts by kind, and loading placeholders.

The UI component reference has a page per component with every declared state live on both grounds, its code in the four frameworks and its props.

Install and import

The UI components ship in the same adapter packages as the charts, on the ui subpath. Their stylesheet is opt-in: import @silverpoint/grounds/ui.css once, beside styles.css. The Angular adapter takes @angular/forms as a peer, for its ControlValueAccessors.

tsx
import '@silverpoint/fonts/fonts.css';
import '@silverpoint/grounds/styles.css';
import '@silverpoint/grounds/ui.css';

// One subpath per component, or every one from the barrel:
import { SpButton } from '@silverpoint/react/ui/button';
import { SpInput, SpSwitch, SpTabs, SpTabPanel } from '@silverpoint/react/ui';

Values, forms and events

Value components (Input, Checkbox, RadioGroup, Switch, Slider, Rate, Segmented, Tabs) are controlled or uncontrolled. The change event carries the new value itself (a string, a bool or a number), never a DOM event. The native inputs submit under their name, so a plain form works. Tag and Alert take closable and emit a close event.

tsx
import { useState, type FormEvent } from 'react';
import { SpButton, SpInput, SpSwitch, SpTabPanel, SpTabs } from '@silverpoint/react/ui';
import { BarChart } from '@silverpoint/react/bar-chart';
import { LineChart } from '@silverpoint/react/line-chart';

export function Settings() {
  const [city, setCity] = useState('Caracas');
  const [precision, setPrecision] = useState(false);
  const [view, setView] = useState('traffic');
  const mode = precision ? 'precision' : 'ink';

  const save = (event: FormEvent<HTMLFormElement>) => {
    event.preventDefault();
    console.log(Object.fromEntries(new FormData(event.currentTarget))); // { city, precision, … }
  };

  return (
    <form onSubmit={save}>
      {/* onChange receives the value itself, not the DOM event */}
      <SpInput label="City" name="city" value={city} onChange={setCity}
        invalid={city === ''} message="Required: pick a city" />
      <SpSwitch label="Precision" name="precision" checked={precision} onChange={setPrecision} />
      <SpTabs label="Views" value={view} onChange={setView}
        items={[{ key: 'traffic', label: 'Traffic' }, { key: 'errors', label: 'Errors' }]}>
        <SpTabPanel value="traffic"><LineChart title="Traffic" mode={mode} /></SpTabPanel>
        <SpTabPanel value="errors"><BarChart title="Errors" mode={mode} /></SpTabPanel>
      </SpTabs>
      <SpButton type="submit" variant="primary">Save</SpButton>
    </form>
  );
}
FrameworkControlledUncontrolledClose
Reactvalue / checked + onChangedefaultValue / defaultCheckedonClose
Vuev-modeldefault-value@close
Angular[(value)], ngModel, formControl[defaultValue](close)
Reflexvalue= / checked= + on_change=default_value= / default_checked=on_close=

Items are plain objects: { key, label, disabled? } for Tabs, Segmented and RadioGroup, { key, title, description?, status? } for Steps. In Reflex, ui_item(key, label, disabled=False) and step_item(key, title, description=None, status=None) build them.

Grounds, modes and sizes

Every UI component takes ground, substrate, mode, seed and size. Like a chart's, they fall back to the dashboard the component sits in, then to the provider. size is sm, md (the default) or lg. The frame's hand derives from id, so the same id draws the same frame on server and client.

  1. Draw
  2. Ink
  3. Ship
  1. Draw
  2. Ink
  3. Ship
  1. Draw
  2. Ink
  3. Ship

Revenue

Q3
Revenue rose through the afternoon and eased at night.

Revenue

Q3
Revenue rose through the afternoon and eased at night.

Revenue

Q3
Revenue rose through the afternoon and eased at night.

Revenue

Q3
Revenue rose through the afternoon and eased at night.

Keyboard and accessibility

  • A native element underneath: a <button>, an <input>, a <fieldset> of radios. Forms, autofill and assistive technology work as with plain HTML.
  • Tabs, Segmented, RadioGroup and Rate follow the WAI-ARIA patterns: one tab stop, the arrows move (and select), Home and End jump, disabled items are skipped, and dir="rtl" mirrors the arrows.
  • One exact focus ring, drawn on the item, the box or the thumb; at most one heightened element per component.
  • Alerts are alert (warning, error) or status (info, success); Progress is a progressbar; Skeleton sets aria-busy. An indeterminate Progress only moves when reduced motion is off.
  • Verified on the example apps with axe (A/AA), keyboard, native submit, target size and reduced motion.

Server rendering

The components render on the server like the charts. React also ships pure-server entry points for the components that hold no state: @silverpoint/react/server/ui/<name> for card, divider, steps, tag, badge, progress, alert and skeleton.

In Reflex

reflex-silverpoint-react wraps all 17: sp_button, sp_input, sp_checkbox, sp_radio_group, sp_switch, sp_slider, sp_rate, sp_segmented, sp_tabs, sp_steps, sp_card, sp_tag, sp_badge, sp_divider, sp_progress, sp_alert, sp_skeleton, plus sp_tab_panel. UI_COMPONENTS is the catalog (group, states, props), UI_DEMOS the 45 reference states, and ui_demo("radio-group", "selected", ground="cyanotype") renders one: this site's reference pages are built from them.

Tip
A text input controlled from state makes a round trip per keystroke. For long text, prefer default_value and read the value on submit.

This site has no backend (rx.App(enable_state=False)), so its components are bound to client-side state instead of an rx.State:

python
from reflex.experimental.client_state import ClientStateVar
from reflex_silverpoint_react import gauge_arc, sp_slider

# A site exported with enable_state=False has no backend: keep the value in the browser.
LOAD = ClientStateVar.create("load", 64)

sp_slider(label="Load", value=LOAD.value, on_change=LOAD.set_value, marks=[0, 25, 50, 75, 100])
gauge_arc(percent=LOAD.value, caption="Load", title="Capacity")

Props every component takes

React / AngularVueReflexTypeDescription
idididstrThe frame variant and every related id derive from it.
groundgroundgroundLiteral['silverpoint', 'cyanotype'] | str | dict[str, Any]Style ground: "silverpoint" or "cyanotype", or a ground object. Defaults to the dashboard's, then the provider's, or silverpoint.
substratesubstratesubstrateLiteral['cream', 'green', 'blue', 'ochre', 'prussian']Prepared substrate within the ground.
modemodemodeLiteral['ink', 'precision']ink by default; precision disables inking.
seedseedseedstr | intSeed of the frame's hand; overrides id for the frame variant only.
sizesizesizeLiteral['sm', 'md', 'lg']Control size: sm, md (default) or lg.
palettepalettepalettedict[str, str]Colours over the ground's (set with palette=), merged over the dashboard's and the provider's.
classNameclass-nameclass_namestrAdded to the component's root element, after its own classes.