Authoring pages
One command scaffolds a page — the class and its registry line. Then write content; the shell, masthead, and TOC come free.
One command#
rails g docs_kit:page writes the class AND registers it — both derived from the title.
rails g docs_kit:page "Getting Started" --group=GuideThat writes app/views/docs/pages/getting_started.rb (slug getting-started, class GettingStarted) and injects page "Getting Started", group: "Guide" into the Doc registry, so the page is routed and in the sidebar the moment you fill in #content. Every derivation is overridable:
--slug=auth— the URL slug,--view=OauthGuide— the class basename,--eyebrow="Advanced"— the eyebrow (defaults to the group),--registry=Guide— a differently-named registry class.
Re-running is idempotent, and a legacy hash-entries registry is left untouched (the generator prints the entry to add by hand).
A page is a Phlex class#
Subclass DocsUI::Page, declare its metadata, fill in #content.
# frozen_string_literal: true
# Compact class reference — Zeitwerk resolves it through the
# directory-implied namespaces, so no nested-module ceremony.
class Views::Docs::Pages::Guide < DocsUI::Page
title "Guide"
eyebrow "Getting started"
def lead = "One sentence that sits under the page title."
def content
DocsUI::Section("First steps", description: "What this section covers.") do
md <<~'MD'
Prose written as Markdown, styled with the reading rhythm.
MD
DocsUI::Code(<<~SOURCE, filename: "config/routes.rb")
Rails.application.routes.draw do
mount DocsKit::Engine, at: "/docs"
end
SOURCE
end
end
endtitle names the page, eyebrow groups it above the title, and lead is the summary sentence under it. Everything you render lives in content.
The shell (topbar, sidebar, theme switcher), the page masthead, and the On this page TOC are added automatically — you only write the body.
Register the page#
One line in the Doc registry — slug and view derive from the title.
A page shows up once it has a page line in the Doc registry. slug and view derive from the title (both overridable per line), and group: sets its sidebar heading. The generator injects this line for you.
class Doc
extend DocsKit::Registry
path_prefix "/docs"
view_namespace "Views::Docs::Pages"
page "Overview", group: "Getting started"
page "Guide", group: "Getting started"
# overrides win: page "OAuth", group: "Guide", slug: "auth", view: "OauthGuide"
endThe sidebar derives from the registry — set c.nav_registries = { "Docs" => Doc } in the initializer and never hand-write a nav lambda again.
The building blocks#
The DocsUI kit you compose inside #content — and where each one is documented in full.
Inside #content you reach for a small kit. The everyday four — a DocsUI::Section wrapper, md for Markdown prose, DocsUI::Code for a highlighted block, and DocsUI::Callout for an aside — carry most pages. The rest are specialised; each has its own reference page rather than being re-explained here.
| Block | Use for | Full reference |
|---|---|---|
DocsUI::Section(title) | an anchored subsection with a heading (+ optional description:) | Components |
md(source) | a block of GFM Markdown — the everyday prose helper | Markdown authoring |
prose { … } | hand-authored prose (p/ul/code) in a reading-rhythm wrapper | Components |
DocsUI::Code(source) | a Rouge-highlighted code block | Code languages |
example { |ex| … } | multi-language tabbed code | Code languages |
DocsUI::Callout(level) | note / tip / warning boxes | Components |
DocsUI::Table / PropTable | reference tables — headers + rows, or the args preset | Components |
DocsUI::Endpoint / RequestExample | the API-reference kit — a method+path line, client tabs, a fields table | API reference |
The primary argument is always positional — Section("Title"), Code(source, filename:) (the filename picks the language), Header("Title") — with modifiers as keywords (description:, eyebrow:).
For the wrappers that take no argument, use the lowercase page helpers prose / example (and md for Markdown). A lowercase method takes a block without parens, so prose do … end just works. The kit forms DocsUI::Prose() / DocsUI::Example() stay valid — they only need the empty () because a bare DocsUI::Prose do parses as a constant reference (a SyntaxError).
md <<~'MD' parses GFM with commonmarker and emits native Phlex nodes — tables, fenced code (routed through DocsUI::Code), links, all Phlex-escaped. Use a single-quoted heredoc so #{...} stays literal author text. See Markdown authoring for the full vocabulary.The "On this page" TOC#
Built for you from your section headings.
Every DocsUI::Section heading becomes an entry in the automatic On this page table of contents — you never list them by hand.
Override its placement per page with on_page.
class Views::Docs::Pages::Guide < DocsUI::Page
on_page :toggle # :toggle | :panel | :sidebar | false
endSee the On this page reference for every mode and the site-wide default.