Thank you for helping improve inkform-docs! This guide covers everything you need to get started — from running the monorepo locally to shipping a new documentation theme that lands in the CLI's theme picker.
- Prerequisites
- Repository layout
- Development workflow
- CI and quality gates
- How the pieces fit
- Contributing a theme
- Contributing engine features
- Contributing to the CLI
- Pull request process
- Conventions
- Releases (maintainers)
- Source mirror (maintainers)
- Node.js 22+ — required by the framework and CI (see root
engines) - npm — this monorepo uses npm workspaces (not pnpm or Turborepo)
- A GitHub account with access to fork and open PRs against
inkform-dev/framework
git clone https://github.com/inkform-dev/framework.git
cd framework
npm installpackages/
framework/ @inkform/framework — the MDX + OpenAPI engine (published)
cli/ @inkform/cli — the npx scaffolder (published)
templates/
canopy/ @inkform/theme-canopy — dark, green accent (default)
shadcn/ @inkform/theme-shadcn — zinc monochrome
galley/ @inkform/theme-galley — warm paper + ink
examples/
pokeapi-docs/ Full PokéAPI docs site + Playwright e2e tests
markdown-docs/ Markdown/MDX feature reference
inkform-docs/ This framework's own documentation (dogfooded)
archive/templates/ Legacy themes (aurora, fern, cedar, mono, base, galley)
preserved for reference — not scaffoldable via CLI
Only @inkform/framework and @inkform/cli are published to npm. Themes and
examples are private workspace packages used for development and demos.
npm install # install every workspace + run postinstall prune
npm run typecheck # typecheck all packages
npm test # framework unit tests (Vitest)
npm run build # next build across every theme + example
npm run build:examples # build the three demo sites onlyRun a workspace locally:
# Examples
npm run dev --workspace=@inkform/example-pokeapi
npm run dev --workspace=@inkform/example-inkform-docs
npm run dev --workspace=@inkform/example-markdown
# Themes
npm run dev --workspace=@inkform/theme-canopy
npm run dev --workspace=@inkform/theme-shadcn
npm run dev --workspace=@inkform/theme-galleyChanges to packages/framework are picked up immediately in themes and
examples via the workspace link and transpilePackages — no publish step
needed during development.
Scaffold from local templates (essential when developing themes or the CLI):
node packages/cli/bin/index.mjs init /tmp/test-docs --theme canopy --from templates -y
cd /tmp/test-docs && npm install && npm run devOverride the GitHub ref the CLI downloads from with INKFORM_DOCS_REF=<branch>
when testing against a fork or feature branch.
End-to-end tests (manual — deliberately excluded from CI):
npm run test:e2e --workspace=@inkform/example-pokeapiThe e2e suite makes one real request to the live PokéAPI per run. Fine for a developer running it locally once, but not wired into CI to avoid hammering a free, community-funded API on every push.
.github/workflows/ci.yml runs on every PR and push to main:
lint → typecheck → test → build → npm audit --audit-level=high
Run the same commands locally before opening a PR. Nothing CI does that you can't reproduce on your machine.
Note: No workspace has a
lintscript yet — CI runs it with--if-presentso it's a no-op today and will activate automatically once one is added.
┌─────────────────────────────────────────────────────────┐
│ Content (MDX + docs.json + openapi.json) │
└──────────────────────────┬──────────────────────────────┘
│
┌──────────────────────────▼──────────────────────────────┐
│ @inkform/framework — headless engine + component kit │
│ (MDX, OpenAPI, search, MCP, AI, llms.txt) │
└──────────────────────────┬──────────────────────────────┘
│
┌──────────────────────────▼──────────────────────────────┐
│ Theme — Next.js app that imports the kit + theme.css │
└──────────────────────────┬──────────────────────────────┘
│
┌──────────────────────────▼──────────────────────────────┐
│ @inkform/cli — downloads a theme as a standalone app │
└─────────────────────────────────────────────────────────┘
- The framework is headless logic + a themeable component kit. The public
API contract lives in
packages/framework/ARCHITECTURE.md. - A theme is a full Next.js 16 app that imports the kit and restyles it
with a single
app/theme.csstoken file. Routing, components, and page structure are intentionally identical across themes — differences are tokens, fonts, and brand SVGs. - The CLI downloads a theme from GitHub (via giget) and scaffolds it as a standalone project.
New documentation site themes are one of the most valuable contributions you
can make. A well-designed theme that meets the contract below can be added to
the CLI's theme picker so every npx @inkform/cli init user can choose it.
- Distinct visual identity — it should look meaningfully different from Canopy, Shadcn, and Galley. We want real variety, not a fourth green-accent dark theme.
- Polished defaults — sample content, typography, spacing, and responsive behavior should feel production-ready out of the box.
- Token-driven styling — all colors, radii, and density via
--fw-*CSS custom properties inapp/theme.css. No hardcoded hex in components. - Accessible — readable contrast, keyboard-navigable sidebar, sensible focus states. Test light and dark modes if your theme supports both.
- Complete contract — ships every route and integration the contract requires (see below).
Every theme must satisfy the contract in
ARCHITECTURE.md §12.
In short, a theme is a standalone Next.js 16 app that:
- Has
content/docs/docs.json+ sample MDX + a sampleopenapi.json. - Imports
@inkform/framework/styles.cssonce, then overrides--fw-*tokens inapp/theme.css. - Routes
/[[...slug]](docs),/api-reference/[[...slug]](native OpenAPI renderer),/blog,/blog/[slug], and/changelog. - Builds a Pagefind search index via a
postbuildscript and mounts<SearchDialog>,<ThemeToggle>, and<AskAi>. - Ships
/api/ask(AI ask-box, flag-gated),/api/mcp(MCP server),app/llms.txt+app/llms-full.txt,.env.example, and a README with content and deploy guides. - Sets
transpilePackages: ['@inkform/framework']innext.config.ts. - Uses a unique workspace name
@inkform/theme-<name>and"private": true.
Themes differ by theme.css tokens, fonts, and brand assets — not by
re-implementing routing, the shell, or engine integrations.
-
Copy an existing theme as your starting point:
cp -r templates/canopy templates/<your-theme>
Canopy is the default and most complete reference. Use it unless you have a specific reason to start from Shadcn or Galley.
-
Rename the package in
templates/<your-theme>/package.json:{ "name": "@inkform/theme-<your-theme>", "description": "<Your Theme> — one-line description" } -
Restyle — this is where your design work lives:
app/theme.css— override--fw-*design tokens (colors, radius, density)app/layout.tsx— font choicespublic/logo.svg,public/favicon.svg,public/hero.svg— brand assetscontent/docs/docs.json— update the sample site name and accent color
Leave routing (
app/[[...slug]]/,lib/route.tsx,components/) alone unless you have a compelling UX reason and can justify the maintenance cost. -
Verify locally:
npm run typecheck --workspace=@inkform/theme-<your-theme> npm run build --workspace=@inkform/theme-<your-theme> npm run dev --workspace=@inkform/theme-<your-theme> # Scaffold test (from repo root): node packages/cli/bin/index.mjs init /tmp/theme-test --theme <your-theme> --from templates -y
-
Register in the CLI — add your theme to the
THEMESarray inpackages/cli/src/scaffold.mjs:{ value: 'your-theme', label: 'Your Theme', hint: 'one-line description for the picker' },
-
Write a theme README at
templates/<your-theme>/README.mdcovering: local dev, adding content, deploy to Vercel/Amplify, and any theme-specific features (follow the Canopy README as a template). -
Open a PR with screenshots or a short screen recording showing your theme in light and dark mode (if applicable), on desktop and mobile.
Maintainers evaluate theme PRs on:
| Criterion | What we look for |
|---|---|
| Visual quality | Distinct, polished, production-ready defaults |
| Contract compliance | All required routes, integrations, and build scripts present |
| Token hygiene | Styling via --fw-* tokens only; no hardcoded colors in components |
| Build health | tsc --noEmit and next build pass cleanly |
| Scaffold flow | npx @inkform/cli init with your theme produces a working project |
| Documentation | Theme README with content and deploy instructions |
Themes that don't meet the bar can still be shared as community examples — we just won't add them to the CLI picker until they're ready.
Engine changes go in packages/framework/src/.
- Read
ARCHITECTURE.mdfirst — it is the stable API contract that themes, the CLI, and examples all build against. - Implement in
packages/framework/src/. - Add or update Vitest tests for non-trivial logic (
npm test). - Update
ARCHITECTURE.mdif you change the public API (new exports, changed behavior, newdocs.jsonfields). - Verify downstream:
npm run typecheckandnpm run buildfrom the repo root.
If your change affects how themes wire up the engine (new route, new env var, new component prop), update at least one theme as a reference implementation.
The CLI lives in packages/cli/. Key files:
| File | Purpose |
|---|---|
bin/index.mjs |
Entry point (inkform-docs binary) |
src/scaffold.mjs |
Scaffold logic, theme list, giget download |
Test CLI changes locally:
node packages/cli/bin/index.mjs init /tmp/cli-test --theme canopy --from templates -yWhen adding a theme to the picker, only scaffold.mjs needs updating — the
CLI downloads templates from github:inkform-dev/framework/templates/<name>#main
at scaffold time.
-
Fork the repo and create a branch from
main:git checkout -b feat/my-contribution
-
Make your changes following the conventions below.
-
Verify locally before pushing:
npm run typecheck npm test npm run build -
Open a PR against
mainwith:- A clear title using conventional commit style (
feat:,fix:,docs:,chore:) - A description of what changed and why
- Screenshots or recordings for visual changes (themes, UI components)
- A test plan — what you ran and what you verified
- A clear title using conventional commit style (
-
Address review feedback. Maintainers may ask for changes before merging.
-
npm run typecheckpasses -
npm testpasses (if you touchedpackages/framework) -
npm run buildpasses -
ARCHITECTURE.mdupdated (if you changed the engine API) - Theme README written (if you added a theme)
- CLI theme list updated (if you added a scaffoldable theme)
- No build artifacts committed (
.next/,*.tsbuildinfo,node_modules/)
Use conventional commit prefixes:
feat: add harbor theme with nautical tokens
fix: correct sidebar collapse on mobile
docs: update theme contract in ARCHITECTURE.md
chore: bump @inkform/framework to 0.4.1
- Strict mode — no
anythat breaks the build - ESM throughout (
"type": "module"where applicable)
- Framework and theme components: style only via
--fw-*CSS custom properties defined inpackages/framework/src/styles/tokens.cssand overridden in each theme'sapp/theme.css - Icons: Lucide with
strokeWidth={1.75}(or~1.75) - No hardcoded hex colors in theme component files — use tokens
.next/,out/,.vercel/node_modules/*.tsbuildinfo.env.localor any file containing secrets/API keys
@inkform/framework and @inkform/cli are published to npm by
.github/workflows/release.yml, triggered by pushing a version tag
(framework-v0.5.0, cli-v0.5.0). It re-runs the full CI gate against the
tagged commit, then publishes with npm Trusted Publishing — a short-lived
OIDC token, no npm secret stored in this repository, and a provenance
attestation on every release.
Nobody publishes from a laptop. Full procedure and the one-time npm/GitHub
setup: packages/framework/PUBLISHING.md.
This public repo is kept in sync with a private working monorepo via
git subtree. Merged PRs here are pulled back into the source repo, so
contribute normally — open a PR against this repo and it flows upstream.
By contributing, you agree that your contributions are licensed under the repository's MIT License.