This is an EmDash site -- a CMS built on Astro with a full admin UI.
npx emdash dev # Start dev server (runs migrations, seeds, generates types)
npx emdash types # Regenerate TypeScript types from schemaThe admin UI is at http://localhost:4321/_emdash/admin.
| File | Purpose |
|---|---|
astro.config.mjs |
Astro config with emdash() integration, database, and storage |
src/live.config.ts |
EmDash loader registration (boilerplate -- don't modify) |
seed/seed.json |
Schema definition + demo content (collections, fields, taxonomies, menus, widgets) |
emdash-env.d.ts |
Generated types for collections (auto-regenerated on dev server start) |
src/layouts/Base.astro |
Base layout with EmDash wiring (menus, search, page contributions) |
src/pages/ |
Astro pages -- all server-rendered |
Agent skills are in .agents/skills/. Load them when working on specific tasks:
- building-emdash-site -- Querying content, rendering Portable Text, schema design, seed files, site features (menus, widgets, search, SEO, comments, bylines). Start here.
- creating-plugins -- Building EmDash plugins with hooks, storage, admin UI, API routes, and Portable Text block types.
- emdash-cli -- CLI commands for content management, seeding, type generation, and visual editing flow.
The EmDash docs are available as an MCP server at https://docs.emdashcms.com/mcp. When you need to verify an API, hook, config option, field type, or pattern, call search_docs against the live documentation rather than relying on training-data recall. The docs reflect current behaviour; assumptions may not.
This template ships with .mcp.json, .cursor/mcp.json, and .vscode/mcp.json so Claude Code, Cursor, and VS Code auto-discover the docs server. Other tools (OpenCode, Windsurf, etc.) need a manual one-time setup -- see docs.emdashcms.com/docs-mcp.
- All content pages must be server-rendered (
output: "server"). NogetStaticPaths()for CMS content. - Image fields are objects (
{ src, alt }), not strings. Use<Image image={...} />from"emdash/ui". entry.idis the slug (for URLs).entry.data.idis the database ULID (for API calls likegetEntryTerms).- Always call
Astro.cache.set(cacheHint)on pages that query content. - Taxonomy names in queries must match the seed's
"name"field exactly (e.g.,"category"not"categories").
A blog with posts, pages, categories, tags, full-text search, and RSS. Designed for personal writing, technical writing, indie newsletters, and anything where the writing is the product. Editorial-tech aesthetic: confident sans-serif, restrained accent, real article structure with bylines and reading time.
| Page | Path | What it shows |
|---|---|---|
| Home | / |
Featured post hero (large image + excerpt), latest posts grid |
| All posts | /posts |
Article count, full post list with excerpts and tag chips |
| Post detail | /posts/[slug] |
Featured image, title, body, left meta column (authors + date), right TOC + search + categories gutter |
| Search | /search |
Full-text search UI |
| Page | /pages/[slug] |
Static page content (Portable Text) |
| Category | /category/[slug] |
Posts filtered by category |
| Tag | /tag/[slug] |
Posts filtered by tag |
| RSS | /rss.xml |
Generated feed |
postscollection:title,featured_image,content(Portable Text),excerpt(text).pagescollection:title,content(Portable Text). Used for/aboutetc.- Taxonomies:
category,tag. - Single
primarymenu (Home, About, Posts by default).
Site settings have title and tagline -- both render in the header / footer.
Single typeface: Inter on --font-sans, used for everything including headings (with tighter letter-spacing on h1/h2). JetBrains Mono on --font-mono for inline code and code blocks. Body and headings share the same family; weight and size carry the hierarchy.
The accent is #0066cc -- used for links, the post-card title hover, and the search input focus ring. There's also a secondary text colour (--color-text-secondary) and a --color-muted for meta info. Don't add a second accent.
The article layout is the standout feature: a three-column reading view with a left meta column (author bylines, date), centred 680px body column, and a right gutter for search, table of contents, and categories. Don't flatten that into one column on desktop -- the layout signals "this is something to read".
src/styles/theme.css is the only file to edit for visual changes. Every CSS variable from Base.astro is listed there as a commented default -- uncomment and change to override. The dark mode palette is defined inside Base.astro itself; light-mode overrides in theme.css won't affect dark mode. To customise dark mode, add @media (prefers-color-scheme: dark) and :root.dark rules in theme.css.
Fonts are configured in astro.config.mjs under fonts:. To swap the body face, change the name: for the entry bound to cssVariable: "--font-sans". Good alternatives: Geist, IBM Plex Sans, Söhne (if you have a licence), Public Sans. If you want a serif-bodied blog, swap to a humanist serif like Source Serif, Crimson Pro, or Lora -- but then also raise --font-size-base to 1.0625rem for readability.
CSS variables worth knowing:
--color-accent,--color-accent-hover,--color-on-accent,--color-accent-ring--color-bg,--color-bg-subtle,--color-surface,--color-text,--color-text-secondary,--color-muted,--color-border,--color-border-subtle--font-sans,--font-mono--tracking-tight/--tracking-snug/--tracking-wide/--tracking-wider-- letter-spacing tokens used across headings and meta labels--content-width(680px) -- article body column--wide-width(1200px) -- max container--gutter-width(200px) -- right sidebar (TOC) on article pages--meta-col-width(180px) -- left meta column on article pages--avatar-size-{xs,sm,md,lg}-- byline avatar sizes at different scales
- Don't add a second accent colour or coloured section backgrounds. The page should be black, white, and one blue.
- Don't replace Inter with a display sans (Bebas, Anton, etc.). Headings rely on weight contrast, not novelty faces.
- Don't collapse the article gutter on desktop -- it's part of the reading experience.
- Don't use stock blog copy ("Welcome to my blog", "Stay tuned for more"). Write a real tagline that says what this blog is about.
- Don't seed the home page with three identical placeholder posts. If you only have one real post, show one real post.
- Don't enable comments without a plan to moderate them. The template doesn't ship a comments system by default for a reason.