Writing a project widget
How a project adds its own widgets: the folder they live in, what a widget file looks like, how widgets compose and share helpers, and how they are styled. It is a task article under Widgets.
The widgets folder
A project adds widgets in one folder, named by its document file's widgets
field, relative to that file:
- my-project/docs/
- devpage.yml —
widgets: widgets - widgets/
- Chart.tsx — a widget:
<Chart> - Timeline.tsx — a widget:
<Timeline> - format.ts — a helper the widgets import; names no tag
- Chart.tsx — a widget:
- devpage.yml —
kind: site
title: devpage
root: README.md
widgets: widgets # every capitalized .tsx in docs/widgets/ is a widget
Every .tsx file in the folder whose name starts with a capital letter is a
widget, named by its file name: KindCards.tsx is <KindCards>. Files whose
names start with a lowercase letter — format.ts, cards.tsx — are helpers
that widgets import; they name no tag. The field works the same for every kind;
a doc's or deck's widgets are its own.
A project widget can't take a system widget's name: Callout.tsx in a project
is a problem, so system widgets that use Callout keep getting the one they
were written for (the rule).
Notes.tsx is a problem too: Notes is devpage's tag for a slide's
notes.
A widget file
A widget is a React component — its default export — written in TSX. devpage's
own docs have one, docs/widgets/KindCards.tsx; trimmed to its shape:
// docs/widgets/KindCards.tsx
;
;
Sketch is a second component in the same file, drawing each kind's little
picture. Written as <KindCards />, it renders:
- It is just React. Hooks, state, effects and events all work; devpage ships React and runs each widget with it.
- Widgets compose. A widget can render any other widget — a system widget or one of the project's — by name, without importing it.
- Helpers. A widget can import
.tsand.tsxfiles from the same folder (import { format } from "./format"); they are compiled with it. npm packages can't be imported — devpage has no package manager. - Images are referenced by their address from the top of the served folder
(
/docs/images/logo.svg), since a widget appears on many pages.
devpage compiles each widget from TSX when a page needs it, and again when its
file changes — saving a widget or a helper updates every open page that uses
it, like saving a page does, whatever a site's watch list says. There is no
build step. A page of a site has that site's widgets wherever it is shown,
printed on its own included.
Styling
A widget is styled the way any React component is, in either way or both:
- Inline styles —
style={{ borderColor: "var(--accent)" }}. - Its own CSS, optionally, exported from the same file as a string — as
KindCardsdoes above, under its own classes (.kind-cards,.kind-card). devpage adds it to the page once.
The theme defines no classes for widgets to borrow; what a widget shares with
the page is the theme's tokens. Written with them — var(--text),
var(--muted), var(--accent), var(--border), var(--surface),
var(--font), var(--font-mono) — a widget follows the light and dark themes
with nothing else to do; where it must differ beyond the tokens, scope the
rule to :root[data-theme="dark"], which follows the reader's choice in the
theme switch (Themes).
What a failing widget looks like is on Using widgets.