A few weeks ago, I wrote about choosing a documentation platform for the Digital Solution Life Cycle Model and its companion tooling. After evaluating several alternatives, I settled on Astro Starlight.
I still think that was a reasonable conclusion based on the requirements I had at the time, but requirements have a habit of becoming clearer once you actually start building something.
After working with Starlight, I have realized that one of my assumptions was wrong: I thought I wanted a documentation platform with a ready-made theme that I could configure and lightly customize, but what I actually want is something different β I want to own the presentation layer.
That realization changes my choice, but perhaps not as dramatically as it might first appear: I am not moving away from Astro, but moving one level down the stack.
My new direction is:
Astro + custom CSS + custom Astro components + selected shadcn components where they are a natural fit.
The important part is that I am not trying to build another generic documentation framework β I am building the presentation layer that my content needs.

What Changed?
My original evaluation started from a fairly explicit assumption:
I am looking for a ready-made platform, not a framework to build upon.
That assumption is what ruled out building anything myself. It did not specifically favor Starlight over Docusaurus, the other finalist β both are ready-made platforms. What tipped the original decision toward Starlight was familiarity: the toolchain I already worked with, not the ready-made criterion itself.
Starlight provides a lot out of the box β navigation, sidebars, search, responsive layouts, Markdown integration, syntax highlighting, table of contents, internationalization, and many other things are already solved β and that is valuable because every capability I do not have to build is something I do not have to maintain.
My original reasoning was therefore that I should spend my time writing and maintaining content rather than building the platform used to publish it, and I still agree with that principle.
What has changed is my understanding of where the boundary between platform and presentation should be: as I started working with Starlight, I increasingly found myself working against the theme rather than with it, wanting different layouts, different ways of presenting navigation, different visual relationships between content elements, and more control over how individual concepts are presented.
None of this means that Starlight is doing something wrong β quite the opposite. Starlight is opinionated because that is part of its value. It provides a coherent documentation experience without requiring every project to design one from scratch.
The problem is simply that my requirements are becoming opinionated as well, and when the opinions of the content and the opinions of the theme start diverging, customization becomes increasingly awkward.
The Presentation Is Part of the Content
This has led me to a more fundamental realization: for what I am building, presentation is not merely decoration around the content β it is part of how the content communicates.
A traditional documentation site often has a fairly predictable structure:
- Sidebar
- Page
- Table of contents
- Previous and next navigation
- Search
That structure works extremely well for technical documentation, but I am increasingly thinking about my sites as something between a book, knowledge base, reference model, and website.
Different types of information should be allowed to look and behave differently: a principle might deserve one presentation, a definition another, and a model might need a large visual representation while an example needs to sit alongside the concept it explains.
Some pages might benefit from a traditional documentation layout, while others might need significantly more horizontal space or an entirely different structure.
Once I started thinking this way, trying to make everything fit into an existing documentation theme stopped making sense.
Keeping Astro
The interesting part of the decision is that I do not feel the same restriction from Astro itself β in fact, the experience has made me more convinced that Astro is the right foundation.
The fundamental characteristic of these sites has not changed:
They are content-first, statically generated websites.
Static Site Generation remains a hard requirement for me: I want pages to be generated ahead of time and served as ordinary HTML wherever possible, and I do not want the basic reading experience to depend on application servers, server-side rendering, or large amounts of client-side JavaScript. Astro fits that model extremely well.
It also gives me the pieces I actually want from a framework:
- Static Site Generation
- Content Collections
- Markdown and MDX
- File-based routing
- Layouts
- Reusable components
- Build-time processing
- TypeScript
- Selective client-side interactivity
Most importantly, Astro does not require me to adopt a particular visual model β an Astro component can simply generate HTML. That simplicity is exactly what I want.
Building a Theme Without Building a Theme
I have been describing the new approach as building my own βtheme,β although that is slightly misleading, since I do not intend to create a generic Astro theme: there will not be a huge configuration file containing switches for every conceivable use case, and I do not need dozens of layout variations in case some hypothetical future project needs them.
Instead, I want to create a relatively small collection of layouts and components designed specifically around the way I publish content.
The architecture becomes something like this:
Astro
β
βββ Content Collections
β βββ Markdown / MDX
β
βββ Layouts
β βββ Book
β βββ Article
β βββ Reference
β
βββ Components
β βββ Navigation
β βββ TableOfContents
β βββ PreviousNext
β βββ Definition
β βββ Principle
β βββ Callout
β βββ Figure
β βββ Diagram
β βββ VersionBanner
β
βββ CSS
β βββ Tokens
β βββ Typography
β βββ Layout
β βββ Components
β
βββ Selected shadcn components
βββ Where they solve a specific UI problem well
The distinction is important: I am not replacing Starlight by writing my own Starlight β I am removing the abstraction that I no longer want.
Owning CSS and Components
The same principle applies below the architecture, to CSS and components directly: I want the visual foundation to be custom CSS, since modern CSS is extraordinarily capable and the number of fundamental layouts required for a content site is relatively small.
I would rather define the design system explicitly:
:root {
--content-width: 46rem;
--page-width: 90rem;
--sidebar-width: 17rem;
--space-xs: 0.25rem;
--space-sm: 0.5rem;
--space-md: 1rem;
--space-lg: 2rem;
--space-xl: 4rem;
--font-body: ...;
--font-heading: ...;
--font-mono: ...;
}
and then build the site around those primitives.
For a content-heavy site, typography, whitespace, rhythm, hierarchy, and responsive behavior matter much more to me than having hundreds of predefined utility classes, and custom CSS also gives me something particularly important in this project: control without another abstraction layer.
If I want to change how a chapter behaves at a particular viewport width, I change the CSS; if I want a figure to escape the normal content column, I change the CSS; and if I want definitions to behave differently from examples, I define those semantics directly β there is no theme API in between the design and the browser.
Components follow the same rule: my default component should be an Astro component.
A component such as a callout, definition, figure, chapter navigation, or table of contents generally does not need client-side JavaScript β it needs to produce good semantic HTML.
For example:
---
interface Props {
title: string;
}
const { title } = Astro.props;
---
<aside class="definition">
<h3>{title}</h3>
<div class="definition-content">
<slot />
</div>
</aside>
That is almost boring, and boring is good. The browser receives HTML and CSS, Astro disappears after the build, and I want that to be the default architecture.
Where shadcn Fits
There is one part of the stack where I do not think βcustomβ should become an ideology: some interface components are deceptively difficult to build well β dialogs, dropdown menus, popovers, command palettes, tooltips, and similar interactive controls involve accessibility, keyboard navigation, focus management, positioning, and numerous edge cases. There is very little value in rebuilding those from scratch simply so I can say that everything is custom, and this is where I see shadcn fitting into the architecture.
But importantly, I do not want shadcn to define the design system β I want to use selected shadcn components as implementation references and component primitives when they are a perfect fit, and that distinction matters.
The architecture is not:
Astro
βββ shadcn
βββ My website
It is:
Astro
βββ My design system
βββ Custom CSS
βββ Custom components
βββ Selected shadcn components
For example, if I need a mobile navigation drawer and a shadcn component solves exactly that problem, using it makes sense.
The same might apply to:
- Dialogs
- Dropdown menus
- Popovers
- Tooltips
- Command menus
- Tabs
- Accordions
But I would not use a shadcn Card simply because a Card exists β if a Principle component has a particular semantic and visual meaning in my content, it should be my component.
That gives me a simple rule:
Custom by default. shadcn when it is already the right solution.
I think this provides a healthier balance than either extreme.
HTML First
There is another architectural principle behind this decision:
HTML first.
I want the final output to contain as little machinery as possible: if something can be expressed as semantic HTML and CSS, that should be the solution, and if it requires behavior, JavaScript can be introduced.
That means the progression should generally be:
HTML
β
CSS
β
Astro component
β
JavaScript
rather than immediately reaching for a JavaScript component framework.
This is particularly appropriate for documentation and book-like websites because most interactions are ultimately about navigating and reading documents.
A table of contents is primarily a list of links, as are breadcrumbs and chapter navigation. A definition, a figure, and a callout are simply content.
The fact that these things are reusable components during development does not mean they need to become application components in the browser β Astro makes that distinction particularly natural.
What About SvelteKit?
I considered SvelteKit as an alternative β I like Svelte, and SvelteKit can generate a completely static site, which would certainly give me all the control I am looking for.
But the question is not whether SvelteKit can do this β it is what I gain by using it.
These sites are fundamentally publishing systems rather than web applications: I do not currently need server-side application state, API routes, authentication, forms, sessions, or most of the other capabilities that make an application framework attractive.
Choosing SvelteKit would therefore mean starting with an application framework and configuring it to behave like a static content framework, whereas Astro starts from the opposite direction: it assumes that HTML is the default and that interactivity should be added where necessary, which is much closer to what I am trying to achieve. And if I eventually encounter a component that genuinely benefits from Svelte, Astro also gives me the option of introducing a Svelte island without rebuilding the rest of the site around Svelte, so there is very little pressure to make that decision upfront.
Reconsidering My Original Criteria
Perhaps the most interesting outcome is that I do not think my original evaluation criteria were wrong β they were incomplete.
I originally emphasized things such as:
- Static Site Generation
- Markdown
- Navigation
- Search
- Versioning
- Sitemap generation
- RSS
- Diagram support
- Maintainability
I still care about all of them, but what I underestimated was another requirement:
Complete control over information architecture and presentation.
That requirement deserves much more weight than I originally gave it. For a conventional documentation site, I would still happily recommend starting with Starlight, but when the structure and visual presentation of the knowledge itself become part of what you are designing, an opinionated documentation theme becomes less attractive.
The balance changes.
The Cost, and Where the Boundary Now Sits
Of course, this decision is not free: Starlight solves many problems that I will now have to solve myself. I need to own navigation, responsive behavior, the table of contents, and previous and next navigation, and I need to integrate search, think about accessibility, test layouts, and maintain the components β that is real work.
Solving them myself does not mean solving them from a blank page, though. Starlight, Docusaurus, and other documentation themes are open source, and there is no reason not to read their markup, their accessibility handling, and their responsive behavior for inspiration before writing my own version. What changes is not where the ideas come from, but who is responsible for maintaining the result.
The danger would be spending months creating a beautiful generic publishing framework instead of publishing anything, and I want to avoid that.
The solution is to keep the scope deliberately narrow: I will build a component when the content requires it, generalize it when multiple pieces of content require the same thing, and extract a reusable abstraction only when I actually have something to reuse. And when an existing component, such as one from shadcn, solves the problem perfectly, I will use it rather than rebuilding it β no speculative framework building.
None of this means I am abandoning the reasoning behind my original decision. I still do not want to spend my time maintaining unnecessary infrastructure, I still want Static Site Generation and Markdown, and I still want to use existing solutions wherever they genuinely remove work without restricting what I am trying to create.
What has changed is where I draw the boundary.
Previously, that boundary was:
My content starts where Starlight ends.
Now it is:
My presentation starts where Astro ends.
Astro owns the build system, content pipeline, routing, and generation. I own the layouts, components, typography, and visual language, shadcn provides selected interface primitives when they fit naturally, and the browser ultimately receives mostly HTML and CSS β that feels like a much better separation of responsibilities.
The New Decision
My revised technology choice is therefore:
Astro + custom CSS + custom Astro components + selected shadcn components.
Not because Starlight is a bad documentation platform β it remains an excellent choice for exactly the problem it is designed to solve. The problem is that I have discovered I am building something slightly different: I do not just need somewhere to publish documentation, I want to build a particular way of reading, navigating, understanding, and exploring knowledge, and for that, the presentation layer needs to belong to the project.
So I am keeping Astro β I am just taking the theme back.
Chosen a ready-made platform and later found yourself fighting the theme instead of the problem? Leave a comment below, or reach out through another channel if you would rather keep the conversation private. Follow the RSS feed for more as the presentation layer takes shape.