Version 1 of 1 · · razie via designer-26designer · Current version

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

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

  • A topic is a page of markdown. Its id is Category:name, or just name for 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). A PUT replaces 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>/publish makes it live. Edits to existing topics follow the project's draft rules (a draft needs If-Match: "none" the first time, then "d<n>").
  • Check the answer. errors, domain and objects in 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

Most app pages need no HTML at all.

  • ```table shows 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.
  • ```cards makes a row of tiles, one line each: icon | title | link | text (soon for 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 a Story: topic), a page with its table, and a card on Home.

Built-in blocks

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

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 fetch sources 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

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/v2 routes only).
  • Keep the data in objects and the logic in rules; the page shows and triggers.
  • layout: full in its frontmatter gives it the whole window (its own navigation, a small button home).

Styling: where CSS can go

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 ```style block (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:Base on base d2, in its ```css block. 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 bare h2 or .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.
  • 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 (with display:flex on 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-type finds the last step;
    • ::before / ::after add icons: an SVG as a data: 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.