Skip to content

Styling

The cascade layers of the stylesheet, the classes and the layout variables you can override, and the attributes of the html element that the layouts read.

@contour-ui/fumadocs/styles.css holds every style of the package: the base of the page, Prose, the MDX components and the layout. Like @contour-ui/react/styles.css, it is plain CSS that reads the tokens, so it follows the theme, and it relies on no reset.

Cascade layers

The stylesheet starts with the order of every cascade layer of contour-ui:

@layer contour.fumadocs-base, contour.fumadocs-prose, contour.components, contour.fumadocs;
LayerHoldsRanks
contour.fumadocs-basethe page itself: the background and the text of bodylowest
contour.fumadocs-prosethe typography of the content, Proseabove the page
contour.componentsthe components of @contour-ui/reactabove Prose: components in the content keep their styles
contour.fumadocsthe MDX components and the layoutabove the components: they adjust the parts they are made of

A layer ranks where it first appears, so this order holds only when the stylesheet loads after the tokens and before @contour-ui/react/styles.css (Load the stylesheets).

Every layer sits inside contour, so your CSS outside any layer overrides them all, whatever its selectors:

app.css
/* Wins over the rule of the layout: same selector, but not in a layer. */
.contour-docs-sidebar__panel {
  background: var(--ct-color-bg-canvas);
}

What Installation says about resets and Tailwind CSS holds here too: a reset goes in a layer declared before contour, and with Tailwind, @layer theme, base, contour, components, utilities; places the whole contour layer, inside which the layers keep the order of this stylesheet.

The page

The base styles the page and nothing more: body gets the background, the color and the font of the tokens, is at least as high as the viewport, and loses its margin. Every other element keeps the defaults of the browser, as in the tests of the packages, so the styled components look the same in your pages as there; Prose styles the content.

Classes

Each part of the layouts has a class, contour-docs-<part>, and its elements contour-docs-<part>__<element>. They are public: style them from your CSS. The styled components inside the parts keep their own classes, such as contour-button, next to these.

ClassElement
contour-docs-layoutthe root of DocsLayout, which sets the layout variables
contour-docs-sidebarthe sidebar: __panel, __header, __search, __body, __footer, __toolbar
contour-docs-headerthe header, data-variant="docs" or "site": __nav, __nav-list, __link, __actions
contour-docs-mobile-nav__footerthe foot of the menu
contour-docs-section-switcherthe button of the switcher; its popover: __content, __list, __link, __name, __description
contour-docs-page-treethe navigation of the pages: __list, __group, __label, __folder, __link
contour-docs-page-tree__collapsiblea folder that opens: __folder-row, __folder-link, __folder-toggle, __folder-trigger
contour-docs-pagethe root of DocsPage: __toc-bar, __body, __article, __content, __toc
contour-docs-page-headerthe title and the description: __title, __description
contour-docs-page-footerthe previous and the next page: __link, __label
contour-docs-toc-panelthe column of the table of contents
contour-docs-toc-triggerthe button of the bar of the table of contents
contour-docs-toc-popoverthe popover of that button
contour-docs-home-layoutthe root of HomeLayout, which sets its layout variables: __main
contour-docs-brandthe name of the site, a link home
contour-docs-site-linksthe icon links of the site
contour-docs-search-triggerSearchTrigger in the field variant
contour-docs-searchthe content of the search dialog
contour-docs-theme-toggleThemeToggle, data-mode="light-dark" or "light-dark-system"
contour-docs-skip-linkSkipLink
contour-docs-proseProse
contour-docs-not-prosethe opt-out of Prose, on any element
contour-docs-previewPreview
contour-docs-cards, contour-docs-cardCards, Card
contour-docs-tabsTabs: __header, __label, __list, __trigger, __content
contour-docs-code-tabsCodeBlockTabs: __list, __trigger, __content
contour-docs-headinga heading of the content: __link, __hash

Layout variables

The roots of the layouts set custom properties that their parts read. Set them on the root from your CSS:

app.css
.contour-docs-layout {
  --contour-docs-sidebar-width: 18rem;
  --contour-docs-article-width: 48rem;
}
VariableDefaultSets
--contour-docs-sidebar-width16remthe width of the sidebar
--contour-docs-article-width44remthe widest the article gets: a readable line of prose
--contour-docs-toc-width13remthe width of the column of the table of contents
--contour-docs-page-inset1rem, 2rem from 48remthe side padding of the page and the header
--contour-docs-header-height3rem, 0px from 48remthe height of the sticky header
--contour-docs-home-width80remthe width of the column of a HomeLayout page and its header
--contour-docs-toc-bar-height2.5rem, 0px from 80rem and on a page without onethe height of the bar of the table of contents
--contour-docs-toolbar-clearancethe width of the toolbar and a gapthe room a collapsed sidebar leaves at the start of the page

HomeLayout sets --contour-docs-home-width: the width of the column its header lays its content out in, which a column of your page reads to line up with it (the column of the page). It sets --contour-docs-page-inset and --contour-docs-header-height as well: the same inset, and a header 3rem high on phones and 4.5rem from 48rem.

app.css
/* A narrower column on the pages of HomeLayout, their header included. */
.contour-docs-home-layout {
  --contour-docs-home-width: 72rem;
}

A value you set holds at every width: set the ones the layout changes at a breakpoint in a media query of your own.

The heights of the sticky bars are for content that a link scrolls to: headings keep their sum free at the top, and an element of yours can do the same.

app.css
.release-note {
  scroll-margin-block-start: calc(
    var(--contour-docs-header-height) + var(--contour-docs-toc-bar-height)
  );
}

The attributes of the page

What the user chose is on <html>, as attributes that docsScript() sets before the first paint and the layouts keep from then on:

AttributeMeansStored in localStorage under
data-themethe theme in use, light or darkcontour-theme, the storageKey of theme
data-contour-docs-sidebar="collapsed"the sidebar is collapsedcontour-docs-sidebar, the sidebarStorageKey

The layouts render every state and CSS shows the one the attributes ask for. Your CSS can read them too, so it is right from the first paint:

app.css
/* A wider article while the sidebar is collapsed. */
@media (min-width: 48rem) {
  :root[data-contour-docs-sidebar='collapsed'] .contour-docs-layout {
    --contour-docs-article-width: 52rem;
  }
}

/* A logo for each theme. */
:root[data-theme='light'] .logo-on-dark,
:root:not([data-theme='light']) .logo-on-light {
  display: none;
}

Themes

The styles of the package read semantic tokens only, so the layouts follow the theme of the tokens: dark by default, light with data-theme="light" (Themes). The theme toggle of the layouts switches it through the ThemeProvider that DocsProvider renders; give DocsProvider and docsScript() the same theme to start in another one:

const options = { theme: { defaultTheme: 'dark' } } satisfies DocsScriptOptions;

<script dangerouslySetInnerHTML={{ __html: docsScript(options) }} />;
<DocsProvider site={site} {...options}>
  {children}
</DocsProvider>;

Style your own pages with the same tokens, and they match the layouts in every theme: Styling with tokens.