---
title: Add the search modal to a non-DocSite site
description: "Light up the Pennington.UI search modal on a bare AddPennington host: reference the UI library, serve its scripts, and add a trigger element."
canonical_url: https://usepennington.net/how-to/discovery/search-on-a-bare-host/
sidecar_url: https://usepennington.net/how-to/discovery/search-on-a-bare-host.md
content_hash: sha256:aee189699af30710372c2cec2b3f68fc292f26d81886ea141d7b67bc4f4abe4d
tokens: 2547
uid: how-to.discovery.search-on-a-bare-host
reading_time_minutes: 4
---

Guides
# Add the search modal to a non-DocSite site

Light up the Pennington.UI search modal on a bare AddPennington host: reference the UI library, serve its scripts, and add a trigger element.

 
`AddDocSite` ships a search modal wired up for you. On a bare `AddPennington` host you wire it yourself — but the index and the modal already exist, so the work is three pieces of markup, not a search UI. `AddPennington` emits the index at `/search/{locale}/index.json`; `Pennington.UI` carries the modal in `scripts.js`; and `Pennington.MonorailCss` already safelists the modal's styles. This guide connects them. For how that index is built and queried, see [How the search index is built and queried](https://usepennington.net/explanation/discovery/search.md).

 
## Before you begin

 
 - A bare `AddPennington` host styled with [MonorailCSS](https://monorailcss.github.io/MonorailCss.Framework/) — see [Style the site with MonorailCSS](https://usepennington.net/tutorials/getting-started/styling.md)
 - The host already serves `/search/{locale}/index.json` (it does, on every `AddPennington` host). To shape what that index contains, see [Tune what the search box returns](https://usepennington.net/how-to/discovery/search.md)
 
 
The `BareHostSearchExample` mounts the shared Bramble corpus and lights up the modal with the wiring below.

 
---

 
## Steps

 
**Reference `Pennington.UI` and `Pennington.MonorailCss`.**

 
`Pennington.UI` serves `scripts.js` (the modal) and, transitively, the `DeweySearch.Web` browser client as static web assets under `/_content/`. `Pennington.MonorailCss` carries the modal's styles.

 
```xml:symbol
<Project Sdk="Microsoft.NET.Sdk.Web">
  <PropertyGroup>
    <TargetFramework>net10.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
    <RootNamespace>BareHostSearchExample</RootNamespace>
  </PropertyGroup>
  <ItemGroup>
    <ProjectReference Include="..\..\src\Pennington\Pennington.csproj" />
    <ProjectReference Include="..\..\src\Pennington.MonorailCss\Pennington.MonorailCss.csproj" />
    <!-- Brings scripts.js (the search modal) and, transitively, the DeweySearch.Web
         browser client as static web assets under /_content. -->
    <ProjectReference Include="..\..\src\Pennington.UI\Pennington.UI.csproj" />
  </ItemGroup>
  <ItemGroup>
    <!-- No local Content/ — this example mounts the shared Bramble corpus.
         Watch that folder so dev-time live reload sees edits to it. -->
    <Watch Include="..\_shared\Bramble\Content\**\*.*" />
    <Watch Include="Components\**\*.razor" />
  </ItemGroup>
</Project>
```

 
**Serve the `/_content/` static assets.**

 
`UsePennington` mounts your content folders, not the RCL assets. Call `app.MapStaticAssets()` so `/_content/Pennington.UI/scripts.js` and `/_content/DeweySearch.Web/dewey-search.js` are served — `scripts.js` fetches the latter on demand when search first opens.

 
```csharp:symbol
using BareHostSearchExample.Components;
using Pennington.FrontMatter;
using Pennington.Infrastructure;
using Pennington.MonorailCss;
  
var builder = WebApplication.CreateBuilder(args);
  
// A bare AddPennington host — no DocSite. AddPennington already emits the search
// index at /search/{locale}/index.json (term shards + per-page fragments); the
// only thing this example adds is the Pennington.UI search modal on top of it,
// wired up in MainLayout.razor. Content is the shared Bramble corpus mounted at
// the root, with the blog subtree excluded because its date/author front matter
// is not part of DocFrontMatter.
builder.Services.AddPennington(penn =>
{
    penn.SiteTitle = "Bramble";
    penn.ContentRootPath = "../_shared/Bramble/Content";
  
    penn.AddMarkdownContent<DocFrontMatter>(md =>
    {
        md.ContentPath = "../_shared/Bramble/Content";
        md.BasePageUrl = "/";
        md.ExcludePaths = ["blog"];
    });
});
  
builder.Services.AddMonorailCss(_ => new MonorailCssOptions
{
    ColorScheme = new NamedColorScheme
    {
        PrimaryColorName = ColorName.Emerald,
        AccentColorName = ColorName.Amber,
        BaseColorName = ColorName.Slate,
    },
});
  
builder.Services.AddRazorComponents();
  
var app = builder.Build();
  
app.UsePennington();
app.UseMonorailCss();
app.UseAntiforgery();
  
// Serve the static web assets Pennington.UI and DeweySearch.Web ship under /_content
// (scripts.js, dewey-search.js). UsePennington only mounts the content folders.
app.MapStaticAssets();
app.MapRazorComponents<App>();
  
await app.RunOrBuildAsync(args);
```

 
**Load the script and add the trigger.**

 
In your layout, load `scripts.js` (`defer`), set `data-default-locale` on `<body>`, and add a trigger element with `id="search-input"`. `scripts.js` self-initializes on load: it binds the click and the Ctrl/Cmd-K shortcut to that element, reads the locale attribute to locate the index, and pulls in `dewey-search.js` on demand the first time the modal opens — so you don't reference that script yourself.

 
```razor:symbol
@* The search modal ships in Pennington.UI/scripts.js and styles itself from the
   @apply blocks Pennington.MonorailCss safelists, so lighting it up on a bare
   (non-DocSite) host is three pieces of markup:
     1. scripts.js in <head> — it pulls in DeweySearch.Web's client on demand the
        first time search opens, so there's no separate dewey-search.js tag,
     2. data-default-locale on <body> — the client reads it to find the index, and
     3. a trigger element with id="search-input".
   scripts.js self-initializes on load and binds the click + Ctrl/Cmd-K shortcut. *@
  
@inherits LayoutComponentBase
  
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <link rel="stylesheet" href="/styles.css" />
    <script src="/_content/Pennington.UI/scripts.js" defer></script>
    <HeadOutlet />
</head>
<body class="bg-base-50 text-base-900 min-h-screen" data-default-locale="en">
    <div class="max-w-3xl mx-auto px-6 py-10">
        <header class="mb-8 flex items-center gap-4 border-b border-base-200 pb-4">
            <a class="text-lg font-bold text-primary-700" href="/">Bramble</a>
            <button type="button"
                    id="search-input"
                    class="ml-auto flex h-9 w-full max-w-xs items-center gap-2 rounded-lg border border-base-200 bg-base-100 px-3 text-sm text-base-500 transition-colors hover:border-base-300 hover:bg-base-200">
                <svg viewBox="0 0 24 24" fill="none" aria-hidden="true" class="h-4 w-4 shrink-0 stroke-current" stroke-width="2">
                    <circle cx="11" cy="11" r="7" />
                    <path stroke-linecap="round" d="m21 21-4.3-4.3" />
                </svg>
                <span class="flex-1 text-left">Search</span>
                <kbd class="ml-auto inline-flex items-center gap-0.5 rounded border border-base-300 px-1.5 py-0.5 font-mono text-[11px] text-base-500">
                    <span>Ctrl</span><span>K</span>
                </kbd>
            </button>
        </header>
        <article class="prose">
            @Body
        </article>
        <footer class="mt-12 border-t border-base-200 pt-4 text-xs text-base-500">
            Bare Pennington host — the search modal comes from Pennington.UI.
        </footer>
    </div>
</body>
</html>
```

 
A single-locale site deployed at the domain root needs only `data-default-locale`. Two more `<body>` attributes cover the other cases:

 
 - **`data-default-locale`** — the default locale code (for example `en`). The client falls back to this when no per-locale prefix matches, so it must always be present.
 - **`data-locales`** — a comma-separated list of every locale code, in any order (for example `en,fr,de`). The client matches the first URL path segment against this list to pick which `/search/{locale}/` tree to query. Leave it empty or omit it on a single-locale site.
 - **`data-base-url`** — the deploy sub-path prefix, with a leading slash and no trailing slash (for example `/docs`). The client prepends it when it fetches `dewey-search.js`, because a runtime-injected `<script>` does not pass through the base-url rewriter that fixes server-rendered links. Omit it for a domain-root deployment.
 
 
A multi-locale site deployed under `/docs` sets all three:

 
```razor
<body data-default-locale="en" data-locales="en,fr,de" data-base-url="/docs">
```

 
---

 
> [!NOTE]
> No search CSS to write. The modal builds its DOM with class names (`.search-modal`, `.search-result`, …) that live only in `scripts.js`, so the MonorailCSS source scan never sees them. `AddMonorailCss` ships their styles anyway, so the modal is styled the moment it appears. A host that brings its own (non-MonorailCSS) stylesheet defines those class names there instead.

 
## Verify

 
 - Run the host and fetch `/_content/Pennington.UI/scripts.js` — it returns JavaScript (`text/javascript`), not the not-found page. If it returns HTML, `MapStaticAssets` is missing or the request is reaching the catch-all route
 - Press Ctrl+K (or click the trigger). The modal opens **styled** — a centered dialog over a dimmed backdrop
 - Type a query. Results deep-link to headings (`/page/#heading`) with a page breadcrumb, and the area chips filter by content area
 
 
## Related

 
 - How-to: [Tune what the search box returns](https://usepennington.net/how-to/discovery/search.md) — configure the same index (exclude pages, weight priority, scope the indexed HTML)
 - Background: [How the search index is built and queried](https://usepennington.net/explanation/discovery/search.md)
 - Background: [What the DocSite and BlogSite templates wire for you](https://usepennington.net/explanation/positioning/docsite-positioning.md)
 - Reference: [SearchIndexOptions](https://usepennington.net/reference/api/search-index-options.md)
 
 
[Previous
                
                Tune what the search box returns](https://usepennington.net/how-to/discovery/search.md)[Next
                    
                Serve the site in multiple languages](https://usepennington.net/how-to/discovery/localization.md)