Customization Guide
Ink is your source code. Here's where to look for the most common tweaks.
Repo layout
src/
components/ # UI components (Sidebar, PageHeader, etc.)
editor/ # Block editor (blocks, commands, keyboard)
features/ # Feature modules (spaces, sharing, tags)
lib/ # Utilities (supabase client, realtime, markdown)
pages/ # Top-level routes
theme.css # Tokens
supabase/
schema.sql # Schema
functions/ # Edge Functions
Common customizations
Change brand colors
File: src/theme.css
:root {
--ink-accent: #f43f5e;
--ink-accent-contrast: #3d0614;
--ink-bg: #0b0f1a;
}Add a custom block type
Blocks are registered in:
File: src/editor/blocks/registry.ts
export const blockTypes = {
paragraph: paragraphBlock,
heading: headingBlock,
// add:
diagram: {
label: "Diagram",
slashTrigger: "diagram",
icon: DiagramIcon,
schema: z.object({ mermaid: z.string() }),
render: ({ data }) => <MermaidRenderer src={data.mermaid} />,
renderEditor: ({ data, onChange }) => (
<MermaidEditor value={data.mermaid} onChange={onChange} />
),
renderMarkdown: ({ data }) => `\`\`\`mermaid\n${data.mermaid}\n\`\`\``,
},
};You also need an entry in src/editor/markdown/parsers.ts for
markdown import.
Add a keyboard shortcut
File: src/editor/keyboard/shortcuts.ts
Swap the storage bucket
Change VITE_INK_STORAGE_BUCKET. Existing uploads remain in the
old bucket; new uploads go to the new one. Write a one-off migrate
script if you need to move historical files (ask Claude Code).
Add an embed handler
File: src/editor/blocks/embed/handlers.ts
export const embedHandlers = {
youtube: { match: /youtube\.com\/watch/, render: renderYoutube },
figma: { match: /figma\.com\/file/, render: renderFigma },
// add:
whimsical: {
match: /whimsical\.com\//,
render: ({ url }) => <iframe src={url.replace("whimsical.com", "whimsical.com/embed")} />,
},
};Change the publish template
File: src/features/publish/templates/
Contains the HTML/CSS scaffold for published pages. Edit the header, footer, or layout.
Restrict what can be embedded
File: src/editor/blocks/embed/allowlist.ts
A hostname allowlist keeps iframes from turning into a security hole.
Tips for working with AI
Ink is heavy on the editor side. When asking Claude Code:
- Think in blocks. Any new UI that renders content should fit the block system. "Add a diagram block" is better than "add a diagram panel".
- Think about realtime. Block schema changes need to merge cleanly — tell Claude to make additions backward-compatible.
- Think about markdown round-trip. If you add a block, it needs markdown import and export, or exports will lose information.
- Think about published output. Blocks that rely on runtime JS need a static fallback for the publish renderer.
Example prompts
Add a Mermaid diagram block. The block stores a Mermaid source string, renders inline using @mermaid-js/mermaid, supports zooming, and round-trips to markdown as a fenced ```mermaid code block. Update the publish renderer to pre-render the SVG.
Add per-space branding: logo, favicon, and accent color at the space level. The publish renderer should use space branding instead of workspace defaults when publishing.
Extract the slash-command matching into a pure-function library so we can cover it with unit tests. Preserve the current command set and ordering.