Theming & Customization Guide

Present

7-min read

Copied Raw Markdown!

Use this guide to change any part of Vyasa's look and behavior. It covers where to place CSS, how scoping works, which elements to target, and how to change behavior safely.

Vyasa has two different theming jobs to do. Sometimes you want to restyle the rendered article itself, for example changing paragraph spacing, code colors, or callout treatment for one folder. Other times you want to restyle the page around the article, for example loading a book-specific font, changing the viewport background, or recoloring the navbar and sidebars for one section. Those two jobs look similar from the outside, but they need different CSS loading behavior.

That is why Vyasa now separates scoped folder CSS from global folder CSS. Scoped CSS is for content-area styling and is attached to the current post section only. Global CSS is for page-level styling and is loaded as a normal stylesheet, so it can legally use html, body, @font-face, @keyframes, and other top-level CSS features.

1) Where to put your CSSURL copied

Vyasa loads custom CSS in this order (later wins):

  1. Framework CSS (bundled in the app)
  2. Root global CSS: global.css, custom.css, or style.css at your blog root
  3. Folder global CSS: global.css inside a folder and its ancestor folders
  4. Folder scoped CSS: custom.css or style.css inside a folder and its ancestor folders

Your blog root is determined by VYASA_ROOT or a .vyasa config file. If neither is set, Vyasa uses the current working directory.

Root-level CSS (site-wide)URL copied

Create or edit:

  • /your-blog-root/global.css
  • /your-blog-root/custom.css (preferred)
  • /your-blog-root/style.css

Use global.css when you need true page-level CSS. Use root custom.css or style.css for the traditional site-wide stylesheet behavior.

Folder-level CSS (scoped)URL copied

Place a custom.css or style.css in any content folder to style the rendered post area for that folder and its subfolders.

Vyasa wraps that CSS inside a scoped selector:

Copied
#main-content.section-your-folder { ... }

This is for selectors that conceptually belong to the post content: headings, paragraphs, tables, images, code blocks, callouts, and other markdown output.

Folder-level global CSSURL copied

Place a global.css in a content folder when the folder needs to style the whole page shell.

Vyasa links that file as a normal stylesheet for every page in that folder subtree. That means it can safely define rules for:

  • html, body
  • #page-container, #site-navbar, #posts-sidebar, #toc-sidebar
  • @font-face
  • @keyframes
  • :root custom properties

This is the correct tool for book themes, app-like section chrome, or folder-specific fonts.

Example mental modelURL copied

For demo/books/flat-land/:

  • global.css sets the parchment viewport background, navbar/sidebar chrome, and custom fonts
  • custom.css styles the chapter page itself: the paper card, headings, paragraph rhythm, and images

If you remember only one rule, remember this: global.css is for the page, custom.css is for the post.

2a) Built-in previous/next navigationURL copied

Vyasa now renders previous/next links automatically for markdown files that have visible sibling markdown files in the same folder.
The pager follows the same folder order Vyasa uses elsewhere, including .vyasa order, sort rules, and visibility filtering.

The pager is added under the rendered article and uses these selectors:

Copied
.vyasa-prev-next
.vyasa-prev-link
.vyasa-next-link

This is especially useful for books, tutorials, and long-form docs where each folder is a linear reading sequence.

2) How to find the section scope classURL copied

Folder CSS is scoped to the #main-content element with a section class derived from the path.

Example:

  • demo/books/flat-land/chapter-01.md
  • Section class on #main-content becomes: section-demo-books-flat-land

To confirm, inspect #main-content in your browser DevTools and copy the class.

3) DOM map (what you can target)URL copied

Use these ids/classes to style specific elements.

How to find selectors for obscure elementsURL copied

If an element isn't listed here, you can find its selector quickly:

  1. Use DevTools first: right‑click the element → Inspect → note the id or classes on the highlighted node.
  2. Search in source: open vyasa/core.py (live app) and vyasa/build.py (static build), then search for the element’s text or a nearby class name.
  3. Search for ids/classes: in the repo, run a text search for the class or id you saw in DevTools.
  4. Follow the builder: most markup is created in layout() or navbar(); those functions assemble the page chrome and are the easiest places to trace where a class or id is set.
  5. Look for generated HTML: if the element is created from Markdown, check render functions like render_footnote_ref() and the tab/mermaid helpers in core.py.

What to do after you find a selectorURL copied

Once you have the selector (id/class/tag), add a rule in custom.css (or a folder custom.css if you want section‑only styling), then reload and refine.

Example:

Copied
<aside id="posts-sidebar" class="hidden xl:block w-72 ...">
Copied
/* Root custom.css */
#posts-sidebar {
  background: #f3f4f6;
  border-radius: 12px;
  padding: 0.5rem;
}

If your rule doesn’t apply:

  1. Increase specificity: target a deeper element (e.g., #posts-sidebar ul)
  2. Add !important to the property being overridden
  3. Check scope: if you used folder custom.css, confirm the page path matches the section

Page structureURL copied

  • #page-container - outer wrapper for the whole page
  • #site-navbar / .vyasa-navbar-shell - sticky header wrapper
  • .vyasa-navbar-card - actual visible navbar card
  • #content-with-sidebars - row containing sidebars + main content
  • #main-content / .vyasa-main-shell - the rendered post content
  • #site-footer / .vyasa-footer-shell - footer wrapper
  • .vyasa-footer-card - actual visible footer card
  • #posts-sidebar / .vyasa-posts-sidebar - left navigation tree shell
  • #toc-sidebar / .vyasa-toc-sidebar - right table of contents shell
  • .vyasa-sidebar-card - collapsible sidebar card
  • .vyasa-sidebar-toggle - sidebar summary/header row
  • .vyasa-sidebar-body - sidebar content panel
  • #sidebar-scroll-container - scrollable list container
  • .toc-link - each TOC link

Mobile panelsURL copied

  • #mobile-posts-panel, #mobile-toc-panel, .vyasa-mobile-panel - off-canvas panels
  • .vyasa-mobile-panel-header, .vyasa-mobile-panel-body - mobile panel sections
  • #mobile-posts-toggle, #mobile-toc-toggle - toggle buttons

Markdown contentURL copied

  • h1h6, p, ul, ol, blockquote, table, code, pre, img
  • .mermaid-wrapper and .mermaid for Mermaid diagrams
  • .sidenote-ref, .sidenote, .sidenote.hl for footnotes
  • .tabs-container, .tabs-header, .tab-button, .tabs-content, .tab-panel for tabs

Put this in root custom.css to define a new global look:

Copied
/* Global background + text */
html, body {
  background-color: #f6f3ee !important;
  color: #1f2937 !important;
}

#page-container,
#main-content {
  background-color: transparent;
  color: inherit;
}

.dark html, .dark body {
  background-color: #0b0f14 !important;
  color: #e2e8f0 !important;
}

/* Typography */
body {
  font-family: "IBM Plex Sans", system-ui, sans-serif;
  line-height: 1.7;
}

h1, h2, h3 {
  letter-spacing: -0.02em;
}

/* Links */
a { color: #0f766e; }
a:hover { color: #115e59; }

Use the explicit hooks instead of positional selectors. Style the visible navbar through .vyasa-navbar-card, not #site-navbar > *.

Copied
.vyasa-navbar-card {
  background-color: #0f766e !important;
  color: #f8fafc !important;
}

.vyasa-footer-card {
  background-color: #1f2937 !important;
  color: #f8fafc !important;
}

#site-navbar a,
#site-footer a {
  color: #f8fafc;
}

#site-navbar a:hover,
#site-footer a:hover {
  color: #e2e8f0;
}

.dark .vyasa-navbar-card { background-color: #0b3b3a !important; }
.dark .vyasa-footer-card { background-color: #111827 !important; }

6) Sidebars and TOCURL copied

Copied
/* Left posts sidebar */
.vyasa-posts-sidebar .vyasa-sidebar-body {
  background: #f3f4f6;
  border-radius: 12px;
  padding: 0.5rem;
}

/* TOC sidebar */
.vyasa-toc-sidebar .vyasa-sidebar-body {
  background: #f8fafc;
  border-radius: 12px;
  padding: 0.5rem;
}

/* TOC links */
.toc-link {
  color: #0f172a !important;
  border-radius: 8px;
}
.toc-link:hover {
  background: rgba(15, 118, 110, 0.12);
  color: #0f766e !important;
}

7) Code blocks and inline codeURL copied

Copied
pre {
  background: #0b1020;
  color: #e2e8f0;
  border-radius: 12px;
  padding: 1rem 1.25rem;
}

code {
  background: rgba(15, 118, 110, 0.12);
  color: #0f766e;
  padding: 0.1rem 0.35rem;
  border-radius: 6px;
}

pre code {
  background: transparent;
  color: inherit;
  padding: 0;
}

8) Mermaid diagramsURL copied

Copied
.mermaid-wrapper {
  background: #f8fafc;
  border-radius: 12px;
  border: 1px solid #e2e8f0;
}

.dark .mermaid-wrapper {
  background: #0f172a;
  border-color: #1f2937;
}

9) Footnotes / sidenotesURL copied

Copied
.sidenote-ref {
  font-size: 0.8rem;
  padding: 0 0.2rem;
  border-radius: 0.25rem;
}

.sidenote {
  font-size: 0.95rem;
  color: #334155;
}

.sidenote.hl {
  background-color: rgba(15, 118, 110, 0.12);
}

10) TabsURL copied

Tabs are styled by built-in CSS, but you can override:

Copied
.tabs-container {
  border-radius: 14px;
  border-color: #cbd5f5;
}

.tab-button.active {
  border-bottom-color: #0f766e;
  color: #0f766e;
}

.tabs-content {
  background: #ffffff;
}

11) Images and mediaURL copied

Copied
img {
  border-radius: 12px;
  box-shadow: 0 8px 24px rgba(15, 23, 42, 0.12);
}

figure > img {
  width: 100%;
  height: auto;
}

12) Change behavior (JS + HTML)URL copied

For behavior changes (animations, interactions, logic), you have two options:

Option A: Custom JSURL copied

Edit:

  • vyasa/static/scripts.js for live app
  • vyasa/build.py (static build) if you need it in static exports

Option B: Inline HTML/JS in MarkdownURL copied

If you need per-post behavior, you can embed raw HTML in markdown:

Copied
<div id="my-widget"></div>
<script>
  // Your custom behavior here
</script>

13) Advanced: change the HTML structureURL copied

If you want to move elements or change layout, edit these functions:

  • vyasa/core.py::navbar() for the header markup
  • vyasa/core.py::layout() for page structure and sidebars
  • vyasa/build.py::static_layout() for static builds

14) TroubleshootingURL copied

If your styles don't apply:

  1. Check your root: Is custom.css in the actual blog root?
  2. Check if CSS is loaded: View page source and confirm /posts/custom.css is present.
  3. HTMX swaps: Folder-scoped CSS is injected into #scoped-css-container and swapped during navigation. If your styles disappear, ensure custom.css exists in that folder.
  4. Utility class conflicts: Use more specific selectors or !important to override Tailwind-style classes.

You now have full control of Vyasa's look and behavior. Start global, then layer scoped CSS for sections, then override specific elements by id/class as needed.