DAVAUX
Alpha

Layouts

A _layout.tsx file wraps every route in its directory and all subdirectories. Layouts are the right place for the HTML shell, navigation, sidebars, headers, footers, and any CSS that should apply globally or section-wide.

Defining a layout

Create _layout.tsx and export a handler via defineLayout. The handler receives children (the rendered page content) and ctx (the full request context):

// src/routes/_layout.tsx
import { defineLayout } from 'davaux'
import type { LayoutProps } from 'davaux'

export default defineLayout(({ children, ctx }: LayoutProps) => {
  return (
    <html lang="en">
      <head>
        <meta charset="utf-8" />
        <title>{ctx.head.title ?? 'My App'}</title>
      </head>
      <body>
        <nav>
          <a href="/">Home</a>
          <a href="/about">About</a>
        </nav>
        <main>{children}</main>
      </body>
    </html>
  )
})

children is typed as Promise<string> — the inner page HTML, already rendered. The JSX runtime awaits it and injects it without HTML-escaping, so {children} works directly. In template literal layouts (returning a string rather than JSX), use const content = await children explicitly.

Nesting layouts

You can have _layout.tsx files at any directory level. Davaux applies them from innermost (closest to the route file) to outermost (closest to the root), so each layout wraps the output of the one inside it:

src/routes/
├── _layout.tsx          ← root layout (HTML shell, global nav)
├── index.page.tsx
└── dashboard/
    ├── _layout.tsx      ← dashboard layout (sidebar)
    └── settings.page.tsx

When a request hits /dashboard/settings, Davaux:

  1. Renders settings.page.tsx
  2. Wraps it with dashboard/_layout.tsx
  3. Wraps that with the root _layout.tsx

Each layout receives the output of the inner layout (or page) as children.

Isolated layouts

By default every _layout.tsx in the ancestor chain applies — a route deep in src/routes/dashboard/ inherits the root layout, the dashboard layout, and any intermediate layouts.

Sometimes you need a completely independent root layout that doesn't inherit from anything above it. Use +layout.tsx for this:

// src/routes/(auth)/+layout.tsx
import { defineLayout } from 'davaux'

export default defineLayout(async ({ children }) => {
  const content = await children
  return (
    <html lang="en">
      <head>
        <meta charset="utf-8" />
        <title>Sign in</title>
      </head>
      <body class="auth-shell">
        <main>{content}</main>
      </body>
    </html>
  )
})

Routes inside (auth)/ receive only this layout. The root _layout.tsx is not applied — the + signals that this layout is the root of its own chain.

+layout.tsx uses the exact same defineLayout API. The only difference is inheritance: _layout inherits from parents, +layout does not.

When to use each

Use _layout.tsxUse +layout.tsx
Layout should inherit the site chrome (nav, footer)Layout needs its own complete HTML shell
Subdirectory variation on a shared themeCompletely different design (auth, dashboard, marketing)
Adding a sidebar or breadcrumbs to a sectionMinimal, fullscreen, or embed layouts

Route groups and layouts

Route groups — directories named with parentheses like (auth)/ — let you apply different layouts to routes that share the same URL depth. The folder name is stripped from the URL entirely, so grouping never changes any route's address.

A typical structure with multiple independent layouts:

src/routes/
  (app)/
    _layout.tsx        ← site chrome — nav, footer, shared styles
    about.page.tsx     → /about
    blog/
      index.page.tsx   → /blog
      [slug].page.tsx  → /blog/:slug
  (home)/
    +layout.tsx        ← isolated — homepage has its own full-bleed layout
    index.page.tsx     → /
  (auth)/
    +layout.tsx        ← isolated — minimal centered shell, no nav
    login.page.tsx     → /login
    register.page.tsx  → /register
  dashboard/
    +layout.tsx        ← isolated — sidebar layout, no site nav
    index.page.tsx     → /dashboard
    settings.page.tsx  → /dashboard/settings

Without route groups, all routes in src/routes/ share a single _layout.tsx. With groups, each concern can have its own layout — or no layout at all — while keeping URLs unchanged.

If you don't need multiple root layouts, skip groups entirely. A single src/routes/_layout.tsx with nested _layout.tsx files in subdirectories is the simplest setup and works well for most apps.

Using ctx.head in a layout

Page handlers set values on ctx.head before the layout runs. Read them in your layout to produce proper <head> tags:

export default defineLayout(({ children, ctx }: LayoutProps) => {
  return (
    <html>
      <head>
        <meta charset="utf-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1" />
        <title>{ctx.head.title ?? 'My App'}</title>
        {ctx.head.description && (
          <meta name="description" content={ctx.head.description} />
        )}
        {Object.entries(ctx.head.meta ?? {}).map(([name, content]) => (
          <meta name={name} content={content} />
        ))}
        {ctx.head.stylesheets?.map((href) => (
          <link rel="stylesheet" href={href} />
        ))}
      </head>
      <body>
        {children}
        {ctx.head.scripts?.map((src) => (
          <script src={src} defer />
        ))}
      </body>
    </html>
  )
})

CSS imports

Import .css files as side effects anywhere in your routes, layouts, or islands. Davaux collects them all and bundles them into a single /_davaux/styles.css file served automatically — no configuration needed.

// src/routes/about.page.tsx
import './about.css'
import { definePage } from 'davaux'

export default definePage((ctx) => <main>...</main>)
// src/islands/Counter.tsx
import './Counter.css'
import { createSignal } from 'davaux/client'

export default function Counter() { ... }
// src/routes/_layout.tsx
import './layout.css'
import { defineLayout } from 'davaux'

export default defineLayout(({ children, ctx }) => {
  return <html>...</html>
})

The <link> tag pointing to the compiled stylesheet is injected into the page automatically, before any island scripts.

Nested layout CSS composes correctly — subdirectory layouts can import section-specific styles without affecting the root layout.

TypeScript

To suppress TypeScript errors on CSS imports, add a src/env.d.ts file:

// src/env.d.ts
declare module '*.css'

Full HTML shell example

A complete root layout suitable for a typical application:

// src/routes/_layout.tsx
import { defineLayout, createLink } from 'davaux'
import type { LayoutProps } from 'davaux'
import './app.css'

export default defineLayout(({ children, ctx }: LayoutProps) => {
  const { Link } = createLink(ctx)
  const year = new Date().getFullYear()

  return (
    <html lang="en">
      <head>
        <meta charset="utf-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1" />
        <link rel="icon" href="/favicon.ico" />
        <title>{ctx.head.title ? `${ctx.head.title} — My App` : 'My App'}</title>
        {ctx.head.description && (
          <meta name="description" content={ctx.head.description} />
        )}
        {ctx.head.stylesheets?.map((href) => (
          <link rel="stylesheet" href={href} />
        ))}
      </head>
      <body>
        <header class="site-header">
          <Link href="/" class="logo">My App</Link>
          <nav>
            <Link href="/docs" activeClass="active">Docs</Link>
            <Link href="/blog" activeClass="active">Blog</Link>
            <Link href="/login">Sign in</Link>
          </nav>
        </header>
        <div class="page-content">
          {children}
        </div>
        <footer>
          <p>© {year} My App. All rights reserved.</p>
        </footer>
        {ctx.head.scripts?.map((src) => (
          <script src={src} defer />
        ))}
      </body>
    </html>
  )
})