Skip to main content
doc0blade47/fumadocsdocumentation home
Sign in

Theme Styles and CSS

Last updated Oct 12, 202675% grounded
Relevant source files

The following files were used as context for generating this wiki page:

Overview

Fumadocs provides a comprehensive styling system built around Tailwind CSS, custom design tokens, and modular stylesheets that configure layouts, typography, and code blocks across base-ui and radix-ui packages. This architecture bridges component primitives with preconfigured or custom theme variants such as Catppuccin, Shadcn, Vitepress, Solar, and Ruby.

Sources: packages/base-ui/css/preset.css:1-11, packages/radix-ui/css/preset.css:1-11, packages/radix-ui/css/vitepress.css:1-40, packages/radix-ui/css/catppuccin.css:1-39, packages/radix-ui/css/solar.css:1-16, packages/radix-ui/css/ruby.css:1-39, packages/radix-ui/css/shadcn.css:1-21

Preset Stylesheets and Base CSS

Preset CSS Structures

The styling architecture relies on preset stylesheets located inside the base-ui and radix-ui packages packages/base-ui/css/preset.css, packages/radix-ui/css/preset.css. Each preset consolidates base imports, typography plugins, and generated layout modules into a single entry point.

Preset FilePrimary Imports & PluginsLayout / Generated Modules
packages/base-ui/css/preset.css./lib/base.css, ./lib/shiki.css, ../dist/tailwind/typography.jsflux.css, docs.css, home.css, notebook.css, shared.css
packages/radix-ui/css/preset.css./lib/base.css, ./lib/shiki.css, ../dist/tailwind/typography.jsdocs.css, home.css, notebook.css, flux.css, shared.css
packages/radix-ui/css/preset-legacy.css./lib/base.css, ./lib/shiki.css, ../dist/tailwind/typography.jsdocs.css, home.css, notebook.css, flux.css, shared.css, legacy theme block

Sources: packages/base-ui/css/preset.css:1-11, packages/radix-ui/css/preset.css:1-11, packages/radix-ui/css/preset-legacy.css:1-22

Base CSS Primitives and Animation Keyframes

The core imported primitives in packages/base-ui/css/lib/base.css define direction-aware drawer offsets, theme variables, and custom animation keyframes packages/base-ui/css/lib/base.css.

@theme {
  --animate-fd-fade-in: fd-fade-in 300ms ease;
  --animate-fd-fade-out: fd-fade-out 300ms ease;
 
  --animate-fd-dialog-in: fd-dialog-in 300ms cubic-bezier(0.16, 1, 0.3, 1);
  --animate-fd-dialog-out: fd-dialog-out 300ms cubic-bezier(0.16, 1, 0.3, 1);
 
  --animate-fd-popover-in: fd-popover-in 100ms ease;
  --animate-fd-popover-out: fd-popover-out 100ms ease;
 
  --animate-fd-sidebar-in: fd-sidebar-in 250ms ease;
  --animate-fd-sidebar-out: fd-sidebar-out 250ms ease;
 
  @keyframes fd-sidebar-in {
    from {
      transform: translateX(var(--fd-sidebar-drawer-offset));
    }
  }
 
  @keyframes fd-sidebar-out {
    to {
      transform: translateX(var(--fd-sidebar-drawer-offset));
    }
  }
 
  @keyframes fd-dialog-in {
    from {
      transform: scale(1.06);
      opacity: 0;
    }
    to {
      transform: scale(1);
    }
  }
 
  @keyframes fd-dialog-out {
    from {
      transform: scale(1);
    }
    to {
      transform: scale(1.04);
      opacity: 0;
    }
  }
 
  @keyframes fd-popover-in {
    from {
      opacity: 0;
      transform: scale(0.9);
    }
  }
 
  @keyframes fd-popover-out {
    to {
      opacity: 0;
      transform: scale(0.9);
    }
  }
 
  @keyframes fd-fade-in {
    from {
      opacity: 0;
    }
    to {
      opacity: 1;
    }
  }
 
  @keyframes fd-fade-out {
    from {
      opacity: 1;
    }
    to {
      opacity: 0;
    }
  }
}

Sources: packages/base-ui/css/lib/base.css:9-85

Note

The sidebar drawer offset variable --fd-sidebar-drawer-offset switches dynamically between 100% and -100% based on the document direction attribute [dir='rtl'] to ensure slide transitions occur in the correct inline orientation.

Sources: packages/base-ui/css/lib/base.css:1-8

Legacy Custom Theme Variables and Utilities

The legacy Radix UI preset provides specific theme tokens and utilities that define container widths, layout offsets, and custom scrollbar or step counters packages/radix-ui/css/preset-legacy.css, packages/base-ui/css/lib/base.css.

@theme {
  --spacing-fd-container: 1400px;
  --fd-page-width: 1200px;
  --fd-sidebar-width: 0px;
  --fd-toc-width: 0px;
  --fd-layout-width: 1600px;
  --fd-banner-height: 0px;
  --fd-nav-height: 0px;
  --fd-tocnav-height: 0px;
}
 
@layer base {
  :root,
  #nd-docs-layout {
    --fd-layout-offset: max(calc(50vw - var(--fd-layout-width) / 2), 0px);
  }
}
 
@utility container {
  margin-inline: auto;
  padding-inline: 1rem;
 
  @apply 2xl:max-w-(--spacing-fd-container);
}
 
/* Apply styles to :root */
@custom-variant on-root {
  :root:has(&) {
    @slot;
  }
}

Sources: packages/radix-ui/css/preset-legacy.css:13-43

Warning

When using legacy layout definitions, the root layout offset calculation utilizes calc(50vw - var(--fd-layout-width) / 2), clamping values at 0px to handle wide-screen alignment correctly.

Sources: packages/radix-ui/css/preset-legacy.css:24-29

Theme Color Palettes

Catppuccin Theme

The Catppuccin theme variant establishes distinct pastel and deep background color tokens inside both packages/base-ui/css/catppuccin.css and packages/radix-ui/css/catppuccin.css packages/base-ui/css/catppuccin.css, packages/radix-ui/css/catppuccin.css. Each variant imports ./lib/default-colors.css and configures custom HSL color properties for both light mode (@theme) and dark mode (.dark) along with specialized sidebar variables packages/base-ui/css/catppuccin.css, packages/radix-ui/css/catppuccin.css.

@theme {
  --color-fd-popover: hsl(220, 22%, 92%);
  --color-fd-popover-foreground: hsl(234, 16%, 35%);
  --color-fd-secondary-foreground: hsl(234, 16%, 35%);
  --color-fd-border: hsl(223, 16%, 83%);
  --color-fd-primary: hsl(266, 85%, 58%);
  --color-fd-primary-foreground: hsl(220, 22%, 92%);
  --color-fd-muted: hsl(220, 22%, 92%);
  --color-fd-card: hsl(220, 22%, 92%);
  --color-fd-accent: hsl(223, 16%, 83%);
  --color-fd-accent-foreground: hsl(234, 16%, 35%);
  --color-fd-card-foreground: hsl(234, 16%, 35%);
  --color-fd-muted-foreground: hsl(233, 10%, 47%);
  --color-fd-foreground: hsl(234, 16%, 35%);
  --color-fd-secondary: hsl(220, 22%, 92%);
  --color-fd-background: hsl(220, 23%, 95%);
  --color-fd-ring: hsl(267, 84%, 81%);
}

Sources: packages/base-ui/css/catppuccin.css:3-20

Note

In Catppuccin dark mode, #nd-sidebar overrides both --color-fd-secondary and --color-fd-muted to hsl(237, 16%, 23%), matching the dark border and accent tone.

Sources: packages/base-ui/css/catppuccin.css:46-49, packages/radix-ui/css/catppuccin.css:46-49

Shadcn and Vitepress Themes

The Shadcn theme variant maps Fumadocs CSS variables directly to underlying --background, --foreground, --muted, --popover, --card, --border, --primary, --secondary, --accent, and --ring properties across :root and .dark selectors packages/base-ui/css/shadcn.css, packages/radix-ui/css/shadcn.css. Additionally, it configures #nd-sidebar bindings and declares a static theme variable --color-fd-error: var(--destructive); packages/base-ui/css/shadcn.css, packages/radix-ui/css/shadcn.css.

#nd-sidebar {
  background-color: var(--sidebar);
  color: var(--sidebar-foreground);
  border-color: var(--sidebar-border);
 
  --color-fd-accent: var(--sidebar-accent);
  --color-fd-accent-foreground: var(--sidebar-accent-foreground);
  --color-fd-ring: var(--sidebar-ring);
  --color-fd-primary-foreground: var(--sidebar-primary-foreground);
}
 
@theme static {
  --color-fd-error: var(--destructive);
}

Sources: packages/base-ui/css/shadcn.css:23-36, packages/radix-ui/css/shadcn.css:23-36

The Vitepress theme variant defines explicit neutral HSL values for light and dark modes, customizes prose links and code background color-mix styles, and modifies #nd-sidebar background colors packages/base-ui/css/vitepress.css, packages/radix-ui/css/vitepress.css.

  --color-fd-background: hsl(0, 0%, 100%);
  --color-fd-card: hsl(0, 0%, 100%);
  --color-fd-foreground: hsl(240, 6%, 25%);
  --color-fd-muted-foreground: hsl(240, 6%, 50%);
  --color-fd-secondary: hsl(240, 6%, 97%);
  --color-fd-accent-foreground: hsl(240, 6%, 25%);
  --color-fd-primary: hsl(226, 55%, 45%);
}

Sources: packages/base-ui/css/vitepress.css:14-21, packages/radix-ui/css/vitepress.css:14-21

Solar and Ruby Themes

The Solar theme variant utilizes OKLCH color definitions for primary and article tokens alongside strict letter-spacing adjustments and responsive layout rules packages/base-ui/css/solar.css, packages/radix-ui/css/solar.css. Under @variant md, Solar injects absolute pseudo-elements before #nd-page with rounded borders, shadows, and margins for both #nd-docs-layout and #nd-notebook-layout packages/base-ui/css/solar.css, packages/radix-ui/css/solar.css.

.dark {
  --color-fd-primary: oklch(0.902 0.0461 259.51);
  --color-fd-article: hsl(0, 0%, 7.04%);
  --color-fd-background: hsl(0, 0%, 5.04%);
}

Sources: packages/base-ui/css/solar.css:12-16, packages/radix-ui/css/solar.css:12-16

The Ruby theme variant implements deep crimson, rose, and pink palettes tailored for light and dark environments packages/radix-ui/css/ruby.css. It establishes primary colors using hsl(348, 85%, 45%) in light mode and hsl(348, 90%, 62%) in dark mode, with custom translucent border alphas set to 0.25 packages/radix-ui/css/ruby.css.

@theme {
  --color-fd-background: hsl(0, 0%, 98%);
  --color-fd-foreground: hsl(350, 45%, 14%);
  --color-fd-primary: hsl(348, 85%, 45%);
  --color-fd-primary-foreground: hsl(0, 0%, 100%);
  --color-fd-border: hsla(348, 40%, 60%, 0.25);
  --color-fd-ring: hsl(348, 85%, 45%);
  --color-fd-accent: hsl(348, 70%, 95%);
  --color-fd-accent-foreground: hsl(348, 70%, 22%);
  --color-fd-muted: hsl(0, 0%, 94%);
  --color-fd-muted-foreground: hsl(348, 25%, 42%);
  --color-fd-secondary: hsl(348, 55%, 92%);
  --color-fd-secondary-foreground: hsl(348, 50%, 20%);
  --color-fd-card: hsl(0, 0%, 100%);
  --color-fd-card-foreground: hsl(350, 45%, 14%);
  --color-fd-popover: hsl(0, 0%, 100%);
  --color-fd-popover-foreground: hsl(350, 45%, 14%);
}

Sources: packages/radix-ui/css/ruby.css:3-20

Caution

When integrating the Solar theme, ensure that table of contents popover positioning rules calculate top offsets using --fd-docs-row-1 or --fd-docs-row-2 variables plus 2.5 * var(--spacing), or popover headers will detach from trigger elements.

Sources: packages/base-ui/css/solar.css:34-68, packages/radix-ui/css/solar.css:34-68

Tailwind Typography Plugin Engine

Selector Manipulation and Scoping

The typography plugin architecture in packages/tailwind dynamically generates scoped prose component styles and custom Tailwind variants packages/tailwind/src/typography/index.ts. Built with Tailwind's plugin.withOptions, the plugin accepts an optional configuration object specifying a custom CSS class name (defaulting to prose) and a disableRoundedTable boolean flag packages/tailwind/src/typography/index.ts. It registers variants across a defined set of selectors using addVariant and injects component styles via addComponents packages/tailwind/src/typography/index.ts.

export const typography: ReturnType<typeof plugin.withOptions<Options>> =
  plugin.withOptions<Options>(({ className = 'prose', ...styleOptions } = {}) => {
    return ({ addVariant, addComponents, ...rest }) => {
      const prefix = (rest as unknown as { prefix: Context['prefix'] }).prefix;
 
      for (const [name, ...values] of SELECTORS) {
        const selectors = values.length === 0 ? [name] : values;
        const selector = selectors.join(', ');
 
        addVariant(
          `${className}-${name}`,
          `& :is(${inWhere(selector, {
            prefix,
            className,
          })})`,
        );
      }
 
      addComponents({
        [`.${className}`]: configToCss(
          {
            ...styles.DEFAULT,
            css: [
              ...(styles.DEFAULT.css ?? []),
              styleOptions.disableRoundedTable ? styles.normalTable : styles.roundedTable,
            ],
          },
          {
            className,
            modifier: 'DEFAULT',
            prefix,
          },
        ),
      });
    };
  });

Sources: packages/tailwind/src/typography/index.ts:149-184

Selector generation relies on inWhere and commonTrailingPseudos to construct robust CSS rules packages/tailwind/src/typography/index.ts, packages/tailwind/src/typography/index.ts. The commonTrailingPseudos function parses selectors with postcss-selector-parser, extracts pseudo-elements into a column-major 2D array, and hoists identical trailing pseudo elements to the end of the selector packages/tailwind/src/typography/index.ts. The inWhere wrapper wraps the selector in a :where() pseudo-class and appends a :not(:where([class~="not-className"],[class~="not-className"] *)) exclusion to support nested unprose exceptions packages/tailwind/src/typography/index.ts.

function inWhere(selector: string, { className, prefix, modifier }: Options & Context) {
  const prefixedNot = prefix(`.not-${className}`).slice(1);
  const selectorPrefix = selector.startsWith('>')
    ? `${modifier === 'DEFAULT' ? `.${className}` : `.${className}-${modifier}`} `
    : '';
 
  // Parse the selector, if every component ends in the same pseudo element(s) then move it to the end
  const [trailingPseudo, rebuiltSelector] = commonTrailingPseudos(selector);
 
  if (trailingPseudo) {
    return `:where(${selectorPrefix}${rebuiltSelector}):not(:where([class~="${prefixedNot}"],[class~="${prefixedNot}"] *))${trailingPseudo}`;
  }
 
  return `:where(${selectorPrefix}${selector}):not(:where([class~="${prefixedNot}"],[class~="${prefixedNot}"] *))`;
}

Sources: packages/tailwind/src/typography/index.ts:21-35

The plugin registers variants for thirty distinct target identifiers using SELECTORS packages/tailwind/src/typography/index.ts, packages/tailwind/src/typography/index.ts.

Variant NameTarget Elements / Selectors
prose-headingsh1, h2, h3, h4, h5, h6, th
prose-h1h1
prose-h2h2
prose-h3h3
prose-h4h4
prose-h5h5
prose-h6h6
prose-pp
prose-aa
prose-blockquoteblockquote
prose-figurefigure
prose-figcaptionfigcaption
prose-strongstrong
prose-emem
prose-kbdkbd
prose-codecode
prose-prepre
prose-olol
prose-ulul
prose-lili
prose-tabletable
prose-theadthead
prose-trtr
prose-thth
prose-tdtd
prose-imgimg
prose-videovideo
prose-hrhr
prose-lead[class~="lead"]
const SELECTORS = [
  ['headings', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'th'],
  ['h1'],
  ['h2'],
  ['h3'],
  ['h4'],
  ['h5'],
  ['h6'],
  ['p'],
  ['a'],
  ['blockquote'],
  ['figure'],
  ['figcaption'],
  ['strong'],
  ['em'],
  ['kbd'],
  ['code'],
  ['pre'],
  ['ol'],
  ['ul'],
  ['li'],
  ['table'],
  ['thead'],
  ['tr'],
  ['th'],
  ['td'],
  ['img'],
  ['video'],
  ['hr'],
  ['lead', '[class~="lead"]'],
];

Sources: packages/tailwind/src/typography/index.ts:117-147

Calculated Styles and Tables

Calculated typographic styles use helper functions round, rem, and em defined in packages/tailwind/src/typography/styles.ts to convert pixel values into relative rem and em units packages/tailwind/src/typography/styles.ts. The engine merges default typography configurations with either roundedTable or normalTable based on the disableRoundedTable option packages/tailwind/src/typography/index.ts. The roundedTable export configures separated borders, background colors using var(--color-fd-card), border radii from var(--radius-lg), and custom inline border start styles packages/tailwind/src/typography/styles.ts.

export const roundedTable = {
  table: {
    borderCollapse: 'separate',
    borderSpacing: '0',
    background: 'var(--color-fd-card)',
    borderRadius: 'var(--radius-lg)',
    border: '1px solid var(--color-fd-border)',
    overflow: 'hidden',
  },
  th: {
    textAlign: 'start',
    padding: 'calc(var(--spacing) * 2.5)',
    'border-inline-start': '1px solid var(--color-fd-border)',
    background: 'var(--color-fd-muted)',
  },
  'th:first-child': {
    'border-inline-start': 'none',
  },
  'th:not(tr:last-child *), td:not(tr:last-child *)': {
    'border-bottom': '1px solid var(--color-fd-border)',
  },
  td: {
    textAlign: 'start',
    'border-inline-start': '1px solid var(--color-fd-border)',
    padding: 'calc(var(--spacing) * 2.5)',
  },
  'td:first-child': {
    'border-inline-start': 'none',
  },
  'tfoot th, tfoot td': {
    borderTopWidth: '1px',
    borderTopColor: 'var(--tw-prose-th-borders)',
  },
  'thead th, thead td': {
    borderBottomWidth: '1px',
    borderBottomColor: 'var(--tw-prose-th-borders)',
  },
};

Sources: packages/tailwind/src/typography/styles.ts:35-72

Note

When disableRoundedTable is enabled, the plugin applies normalTable rules instead of roundedTable, setting explicit bottom borders on table headers and alternating body rows while omitting explicit card background wrappers.

Sources: packages/tailwind/src/typography/index.ts:167-182, packages/tailwind/src/typography/styles.ts:74-113

Syntax Highlighting and Shiki Styling

Shiki Theme Variables and Selectors

Fumadocs formats code blocks and inline code using Shiki via CSS stylesheets located in packages/radix-ui/css/lib/shiki.css and packages/base-ui/css/lib/shiki.css. The engine maps syntax highlighting tokens to CSS variables for light and dark themes. For block-level code elements matching .shiki:not(.not-fumadocs-codeblock *), token spans inherit --shiki-light colors and --shiki-light-font-style in light mode, while .dark .shiki:not(.not-fumadocs-codeblock *) selectors assign --shiki-dark colors and --shiki-dark-font-style. Inline code elements matching code.shiki:not(.not-fumadocs-code *) apply the identical theme variable mapping for both light and dark contexts.

.shiki:not(.not-fumadocs-codeblock *) {
  --padding-left: calc(var(--spacing) * 4);
  --padding-right: calc(var(--spacing) * 4);
 
  code span {
    color: var(--shiki-light);
    font-style: var(--shiki-light-font-style);
  }

Sources: packages/radix-ui/css/lib/shiki.css:1-8, packages/base-ui/css/lib/shiki.css:1-8

.dark .shiki:not(.not-fumadocs-codeblock *) {
  code span {
    color: var(--shiki-dark);
    font-style: var(--shiki-dark-font-style);
  }
}
 
/* inline code */
code.shiki:not(.not-fumadocs-code *) span {
  color: var(--shiki-light);
  font-style: var(--shiki-light-font-style);
}
 
.dark code.shiki:not(.not-fumadocs-code *) span {
  color: var(--shiki-dark);
  font-style: var(--shiki-dark-font-style);
}

Sources: packages/radix-ui/css/lib/shiki.css:92-108, packages/base-ui/css/lib/shiki.css:92-108

Note

The :not(.not-fumadocs-codeblock *) and :not(.not-fumadocs-code *) exclusions prevent Shiki theme overrides from applying inside custom blocks that opt out of Fumadocs styling.

Sources: packages/radix-ui/css/lib/shiki.css:1-1, packages/radix-ui/css/lib/shiki.css:100-100

Line Decoration and Code Block Features

Code block layout is governed by variables setting padding-left and padding-right to 4 times the spacing scale (--padding-left and --padding-right). Empty code lines are forced to a height of 1lh. Advanced features like line focus states, git diff markers, line highlights, and line numbers are implemented through specific CSS classes and pseudo-elements.

Class SelectorApplied Styles & VariablesPurpose
&.has-focused .line:not(.focused)filter: blur(2px); transition: filter 200ms;Applies blur to non-focused lines when focus mode is active.
&.has-focused:hover .line:not(.focused)filter: blur(0);Clears blur when hovering over a code block with active focus lines.
.diff.remove--fd-counter-color: var(--color-fd-diff-remove-symbol); @apply bg-fd-diff-remove; opacity: 0.7;Styles deleted diff lines with background color and opacity.
.diff.remove::beforecontent: '-'; @apply text-fd-diff-remove-symbol;Inserts the minus symbol before removed lines.
.diff.add--fd-counter-color: var(--color-fd-diff-add-symbol); @apply bg-fd-diff-add;Styles added diff lines with background color.
.diff.add::beforecontent: '+'; @apply text-fd-diff-add-symbol;Inserts the plus symbol before added lines.
.highlighted--fd-counter-color: var(--color-fd-primary); @apply bg-fd-primary/10;Highlights entire code lines with primary theme tint.
.highlighted::afterposition: absolute; content: ''; @apply inset-y-0 left-0 border-l-2 border-fd-primary/50;Renders a left accent border on highlighted lines.
.highlighted-wordpadding: 1px; @apply border -my-px border-fd-primary/30 bg-fd-primary/10 rounded-md font-medium;Styles specific inline highlighted words within code spans.
.line {
  position: relative;
  padding-left: var(--padding-left);
  padding-right: var(--padding-right);
}
 
.line:empty {
  height: 1lh;
}
 
&.has-focused .line:not(.focused) {
  filter: blur(2px);
  transition: filter 200ms;
}
 
&.has-focused:hover .line:not(.focused) {
  filter: blur(0);
}
 
.diff::before {
  position: absolute;
  left: calc(var(--spacing) * 1.5);
}
 
.diff.remove {
  opacity: 0.7;
  --fd-counter-color: var(--color-fd-diff-remove-symbol);
  @apply bg-fd-diff-remove;
}
 
.diff.remove::before {
  content: '-';
  @apply text-fd-diff-remove-symbol;
}
 
.diff.add {
  --fd-counter-color: var(--color-fd-diff-add-symbol);
  @apply bg-fd-diff-add;
}
 
.diff.add::before {
  content: '+';
  @apply text-fd-diff-add-symbol;
}
 
.highlighted {
  --fd-counter-color: var(--color-fd-primary);
  @apply bg-fd-primary/10;
}
 
.highlighted::after {
  position: absolute;
  content: '';
  @apply inset-y-0 left-0 border-l-2 border-fd-primary/50;
}
 
.highlighted-word {
  padding: 1px;
  @apply border -my-px border-fd-primary/30 bg-fd-primary/10 rounded-md font-medium;
}

Sources: packages/radix-ui/css/lib/shiki.css:10-69, packages/base-ui/css/lib/shiki.css:10-69

When data-line-numbers is present on the container, line numbers are generated using CSS counters (counter-increment: line), positioning counter values inside a 7-space left column with a color mixed in oklab between --fd-counter-color and transparent.

 
  &[data-line-numbers] .twoslash-meta-line {
    padding-left: calc(var(--padding-left) + 7 * var(--spacing));
  }
 
  &[data-line-numbers] .line {
    counter-increment: line;
    padding-left: calc(var(--padding-left) + 7 * var(--spacing));
  }
 
  &[data-line-numbers] .line::after {
    position: absolute;
    content: counter(line);
    color: color-mix(
      in oklab,
      var(--fd-counter-color, var(--color-fd-muted-foreground)) 60%,
      transparent
    );
    @apply inset-y-0 left-0 w-7 text-right;
  }
}

Sources: packages/radix-ui/css/lib/shiki.css:70-90, packages/base-ui/css/lib/shiki.css:70-90

Specialized Extension Stylesheets

OpenAPI, AsyncAPI, and API Documentation Presets

Supplementary preset stylesheets configure styling rules for API documentation packages by importing shared base definitions and companion package styles. The OpenAPI preset (packages/openapi/css/preset.css) imports ./generated/shared.css and @import '@fumadocs/api-docs/css/preset.css'. Similarly, the AsyncAPI preset (packages/asyncapi/css/preset.css) imports ./generated/shared.css and @import '@fumadocs/api-docs/css/preset.css'. The core API docs preset (packages/api-docs/css/preset.css) imports only ./generated/shared.css.

@import './generated/shared.css';
@import '@fumadocs/api-docs/css/preset.css';

Sources: packages/openapi/css/preset.css:1-2, packages/api-docs/css/preset.css:1-1, packages/asyncapi/css/preset.css:1-2

Note

Both OpenAPI and AsyncAPI stylesheets reuse @fumadocs/api-docs/css/preset.css, ensuring uniform presentation across all API specification rendering packages.

Sources: packages/openapi/css/preset.css:2-2, packages/asyncapi/css/preset.css:2-2

Python Module Theme Configuration

The Python module preset (packages/python/styles/preset.css) sets up Tailwind scanning paths and defines custom inline theme variables for Python syntax elements. It scans distribution output files via @source '../dist/**/*.js' and configures color variables inside an @theme inline block.

Theme VariableValuePurpose
--color-fdpy-funcoklch(79.2% 0.209 151.711)Defines the color token for Python functions.
--color-fdpy-classredDefines the color token for Python classes.
--color-fdpy-attributeoklch(74% 0.238 322.16)Defines the color token for Python attributes.
--color-fdpy-methodblueDefines the color token for Python methods.
@source '../dist/**/*.js';
 
@theme inline {
  --color-fdpy-func: oklch(79.2% 0.209 151.711);
  --color-fdpy-class: red;
  --color-fdpy-attribute: oklch(74% 0.238 322.16);
  --color-fdpy-method: blue;
}

Sources: packages/python/styles/preset.css:1-8

Tailwind V4 Integration and Bundling

Global Style Entry Points and Imports

Tailwind CSS v4 integration across Fumadocs relies on @import 'tailwindcss' statements coupled with layout presets and neutralization layers. The preview package style entry point (packages/preview/src/styles/globals.css) imports Tailwind alongside neutral and preset configurations.

@import 'tailwindcss';
@import 'fumadocs-ui/css/neutral.css';
@import 'fumadocs-ui/css/preset.css';

Sources: packages/preview/src/styles/globals.css:1-3

Both packages/base-ui/css/style.css and packages/radix-ui/css/style.css follow an identical import structure while additionally defining a base layer rule that configures the body element for flex column layout with a minimum height spanning the viewport.

@import 'tailwindcss';
@import './neutral.css';
@import './preset.css';
 
@layer base {
  body {
    @apply flex flex-col min-h-screen;
  }
}

Sources: packages/base-ui/css/style.css:1-9, packages/radix-ui/css/style.css:1-9

Note

The root application layout (packages/preview/src/pages/_root.tsx) imports globals.css and attaches the identical flex column layout utility classes directly to the body element alongside hydration warning suppressions.

Sources: packages/preview/src/pages/_root.tsx:3-11

Vite and Waku Configuration

Build tooling for the preview package is handled via Waku configuration (packages/preview/waku.config.ts), which integrates Vite plugins including @tailwindcss/vite and an internal Fumadocs configuration loader.

vite: {
  resolve: {
    tsconfigPaths: true,
  },
 
  plugins: [
    tailwindcss(),
    {
      name: 'internal',
      config() {
        return getConfig({ root: process.cwd() });
      },
    },
  ],
},

Sources: packages/preview/waku.config.ts:7-21

Oxide Scanner and Dynamic CSS Compilation

The tsdown build configuration for packages/base-ui/tsdown.config.ts incorporates @tailwindcss/oxide via a programmatic Scanner implementation in compileInline(). This scanner extracts utility names from source code patterns and generates layout-specific inline source stylesheets inside css/generated/.

export async function compileInline() {
  await mkdir('css/generated', { recursive: true });
  const scanner = new Scanner({
    sources: [
      {
        base: path.resolve('src'),
        pattern: 'components/**/*.{ts,tsx}',
        negated: false,
      },
      {
        base: path.resolve('src'),
        pattern: 'layouts/shared/**/*.{ts,tsx}',
        negated: false,
      },
      {
        base: path.resolve('src'),
        pattern: '*.{ts,tsx}',
        negated: false,
      },
    ],
  });
 
  const commonNames = scanner.scan();
  await writeFile('css/generated/shared.css', namesToFile(commonNames));
 

Sources: packages/base-ui/tsdown.config.ts:50-74

The compilation loop iterates through distinct layout identifiers (flux, notebook, home, and docs), filters out utilities already included in the shared scanner results, and writes out individual layout CSS files.

  const layouts = ['flux', 'notebook', 'home', 'docs'];
  const commonNameSet = new Set(commonNames);
 
  for (const layout of layouts) {
    const scanner = new Scanner({
      sources: [
        {
          base: path.resolve('src'),
          pattern: `layouts/${layout}/**/*.{ts,tsx}`,
          negated: false,
        },
      ],
    });
 
    await writeFile(
      `css/generated/${layout}.css`,
      namesToFile(scanner.scan().filter((name) => !commonNameSet.has(name))),
    );
  }
 
  console.log('generated CSS files');
}

Sources: packages/base-ui/tsdown.config.ts:75-96

Caution

The Oxide scanner explicitly excludes negated patterns and separates layout components so that shared utility names are compiled once into shared.css while layout-specific classes remain isolated in their respective generated files.

Sources: packages/base-ui/tsdown.config.ts:52-70, packages/base-ui/tsdown.config.ts:78-92

Was this page helpful?