- tags
- #help #apps #pages #style
- order
- 25
- description
- making topics, app pages and HTML pages, and styling them (where CSS can go and where it can't)
Creating apps and pages¶✎ edit
How to make the things people see in a project: plain topics, pages that show data, live HTML, whole-page apps, and the look of them all. Each part says which tool to reach for and its limits.
Topics¶✎ edit
- A topic is a page of markdown. Its id is
Category:name, or justnamefor an ordinary page. Names use letters, digits,-and_. - Write it:
PUT /api/v2/topics/<name>with the markdown as the body (Content-Type: text/markdown). APUTreplaces the whole topic, so read it, change it, write it back. - New topics must be published. Writing a new topic makes a draft nobody else can see:
POST /api/v2/drafts/<name>/publishmakes it live. Edits to existing topics follow the project's draft rules (a draft needsIf-Match: "none"the first time, then"d<n>"). - Check the answer.
errors,domainandobjectsin the reply must be empty; if not, fix and save again. - Frontmatter sits at the top between
---lines:tags:(flat lower-case words, comma-separated),description:, and for special topics their own keys. - Links:
[[Topic]],[[Topic#section]],[[Category:name]],[[target|label]].
Pages that show data¶✎ edit
Most app pages need no HTML at all.
```tableshows a class's objects (class:,columns:,totals:,sort:,filter:,color:). Each row opens its edit form; + Add makes a new one. Drawn with the page: reload for new data.```cardsmakes a row of tiles, one line each:icon | title | link | text(soonfor one that isn't live). The first cards block under a home page's title is its app list.- An app is a
Spec:topic (the model), its objects (often seeded from aStory:topic), a page with its table, and a card onHome.
Built-in blocks¶✎ edit
Some fences are drawn by d2 itself, because they show per-viewer data, change accounts, or carry a secret: ```connect-ai, ```next-step, ```my-projects and the like. A topic places them; it can't rebuild them in HTML (an embed can't use the viewer's session). To change how one looks, style it (below).
Live HTML in a page: embedhtml¶✎ edit
A ```embedhtml fence holds HTML, CSS and a <script>, run in a sandboxed frame inside the page.
- Its CSS stays inside the frame: it styles the widget, never the page around it.
- It gets the page's colours and fonts as variables (
var(--ink),var(--card),var(--line),var(--soft)…): use them so it matches. - It can
fetchsources that allow any origin; it can't read the page, the person's session or the project's API as them. For the project's own data use a table. - Show a clear state when a source doesn't answer, never a blank.
Whole HTML pages: App topics¶✎ edit
An App:<name> topic is a whole HTML page at /app/<name>, under the project's top bar: for a dashboard or a custom editor.
- It runs sandboxed and reaches the project through
await d2.api(method, path, body), as the person looking at it (the project's/api/v2routes only). - Keep the data in objects and the logic in rules; the page shows and triggers.
layout: fullin its frontmatter gives it the whole window (its own navigation, a small button home).
Styling: where CSS can go¶✎ edit
Learned restyling the Onboard your AI steps, 2026-10-04.
<style>in a topic does nothing. d2 strips it from the markdown when it draws the page, silently. (A home page's old "polish" block had never applied.)- A project's own
Style:topic is a theme, not a layer. It sets the colour and font tokens in a```styleblock (known tokens only:--paper --card --ink --soft --line --indigo --indigo-bg --slate --slate-bg --sun --sun-bg --leaf --rose --rose-bg --god --god-bg --admin --admin-bg --attn --display --body --mono), and a person picks it at/styles. Rules put in one don't reach other pages. - Rules that every page gets live in
Style:Baseon base d2, in its```cssblock. Publishing it rebuilds/styles/shared.css(its?h=changes), which every page of every project loads: no coder, no deploy.- Because it reaches everything, scope every rule to the thing you're styling, e.g.
[data-block="connect-ai"] .btn{…}, never a bareh2or.btn. - Shared rules can override a block's own behaviour: a base rule showed a form the block had marked
hidden. Add[data-block="…"] [hidden]{display:none!important}when you restyle a block with hidden parts.
- Because it reaches everything, scope every rule to the thing you're styling, e.g.
- Style what's really there. Before writing CSS for a block, get its real markup in every state it has: a token or a visitor often sees a placeholder, not the block. Log in as a person, click through each state (for connect-ai: before Done, waiting, connected) and read the HTML. Then style its own classes (
.blk-step,.nextstep,.done…). - Restyle, don't rebuild. CSS goes a long way on markup you can't change:
order(withdisplay:flexon the block) puts steps back in order when the markup moves them;:has()picks a part by what it contains (.blk-step:has(details),h3:has([data-reload-while]));:last-of-typefinds the last step;::before/::afteradd icons: an SVG as adata:URL background, animated with@keyframes;- always add
@media (prefers-reduced-motion:reduce){…animation:none}for anything that moves. - What CSS can't do: change text baked into the markup (a heading's "1." stays text).
- Check every state after a change, on the person's real screen: a squeezed button (
white-space:nowrap; flex:none) or an un-hidden form only shows in one of them. - Colours: prefer the page's tokens (
var(--line),var(--soft)); a fixed hex is fine for an accent that must stay the same in every theme.