Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
c758d35
geist and ibm fonts
mikemazara Aug 11, 2026
1be1140
sidebar and nav tabs
mikemazara Aug 11, 2026
65b26da
phase 3 and code cleanup
mikemazara Aug 12, 2026
81d99f5
fix inconsistencies
mikemazara Aug 12, 2026
2e2b923
fix script pattern to regenerate dist
mikemazara Aug 13, 2026
5eeefdc
footer, and alerts
mikemazara Aug 13, 2026
f7a14e4
bcrums, footer, sidebar
mikemazara Aug 13, 2026
22c7b3d
table behave
mikemazara Aug 13, 2026
a089ea4
min w for mobile
mikemazara Aug 13, 2026
4634361
fix lint
mikemazara Aug 13, 2026
314f7d3
a11y
mikemazara Aug 20, 2026
5a774b1
table and responsive
mikemazara Aug 20, 2026
45697d8
table and tests
mikemazara Aug 20, 2026
7d850de
merge 1
mikemazara Aug 20, 2026
5d6121a
pt 2
mikemazara Aug 20, 2026
7e8a422
Match NDS hover, code chip, and footer logo alignment.
mikemazara Aug 24, 2026
9151e89
Merge branch 'update-starlight-docs' into update-starlight-docs-2
mikemazara Aug 24, 2026
dd65971
Match landing hover, guide filters, and 404 to the Figma review.
mikemazara Aug 24, 2026
e76c00e
Match Figma TOC bar, nav underline, and chrome sizes.
mikemazara Aug 25, 2026
be918ec
Merge update-starlight-docs into update-starlight-docs-2.
mikemazara Aug 25, 2026
2d4e3a7
Match mobile hero, guide filters, and homepage cards to Figma.
mikemazara Aug 25, 2026
c2db51d
Merge branch 'update-starlight-docs' into update-starlight-docs-2
mikemazara Aug 25, 2026
bf49132
Keep the 2px tab underline and a named footer logo.
mikemazara Aug 25, 2026
e514c5e
Drop the 12px homepage card override.
mikemazara Aug 25, 2026
3c5a6a9
Hide the nested code-frame titlebar inside tab panels.
mikemazara Aug 25, 2026
ed593e8
Merge branch 'update-starlight-docs' into update-starlight-docs-2
mikemazara Aug 25, 2026
c943e7d
Format the tabbed-code header selector.
mikemazara Aug 25, 2026
79b30ca
Merge branch 'update-starlight-docs' into update-starlight-docs-2
mikemazara Aug 25, 2026
d977407
update links
mikemazara Aug 25, 2026
654d561
Merge remote-tracking branch 'origin/main' into update-starlight-docs
mikemazara Aug 31, 2026
c070ebd
Merge branch 'update-starlight-docs' into update-starlight-docs-2
mikemazara Aug 31, 2026
8dbf24e
align icon
mikemazara Sep 1, 2026
ed171b4
Merge branch 'update-starlight-docs' into update-starlight-docs-2
mikemazara Sep 1, 2026
201bbd6
Untrack .cursor and ignore it
mikemazara Sep 1, 2026
5c85456
Merge remote-tracking branch 'origin/main' into update-starlight-docs-2
mikemazara Sep 1, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file modified .github/screenshots/dark.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified .github/screenshots/light.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,9 @@ dist
# Stores VSCode versions used for testing VSCode extensions
.vscode-test

# Editor/AI assistant local config
.cursor/

# pnpm
.pnpm-store

Expand Down
38 changes: 33 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,8 @@ export default defineConfig({
```

That's it - the plugin wires in the theme CSS, self-hosted fonts, the branded
`SiteTitle`/`Head`/`Footer` components, and a default GitHub social link.
header, splash footer, and a default GitHub social link. Pass `nav` if you want
top-level Docs / Guides / Reference tabs.

### Serving under a subpath

Expand Down Expand Up @@ -106,9 +107,12 @@ only needs `light`.
- **Typography** - Geist for body and headings, IBM Plex Mono for code, both
self-hosted (no external font requests at runtime).
- **Logo, favicon, and footer** - the Nebari mark in the header, an inlined
symbol favicon, and a branded footer on every page.
- **Search** - Starlight's built-in Pagefind, ready to merge additional pack
indexes for portal-wide multisite search.
symbol favicon, and a branded multi-column footer on splash pages (home, 404).
Doc pages with a sidebar end at their content.
- **Nav tabs** - optional `nav` items render as header tabs and in the mobile
drawer. Omit the option and the header stays stock Starlight.
- **Search** - Starlight's built-in Pagefind, styled to the Docs theme, ready to
merge additional pack indexes for portal-wide multisite search.

Everything is overridable: your own `customCss`, `components`, and `social`
entries are merged after the theme's, so a consumer always wins.
Expand Down Expand Up @@ -146,6 +150,19 @@ Run the test suite (builds the package and docs, then runs the tests):
bun test
```

The root `test` script covers `packages/starlight/test` and `docs/test` (the
`--base /demo-pack` build). End-to-end checks live in `docs/`:

```sh
cd docs && bun run e2e
```

Regenerate the README screenshots (out of CI, 1440×900):

```sh
cd docs && bun run screenshots
```

### Linting and formatting

[Biome](https://biomejs.dev) handles both formatting and linting (including
Expand Down Expand Up @@ -196,9 +213,20 @@ This package follows [EffVer](https://jacobtomlinson.dev/effver/)
your build unexpectedly:

```jsonc
{ "dependencies": { "@nebari/starlight": "^0.1.0" } }
{ "dependencies": { "@nebari/starlight": "^0.3.0" } }
```

### 0.3.0

EffVer meso. Four behaviours change without an opt-in:

1. The footer no longer renders on doc pages — splash pages only.
2. `lastUpdated` defaults to `true`, so a date appears on pages that had none.
3. Nav tabs appear only if `nav` is set; absent, the header is byte-identical.
4. Tables scroll rather than wrap. Long values scroll inside the cell or the
table instead of breaking across lines. With JavaScript disabled, a wide
table can overflow the page.

## Releasing

Releases are published to npm automatically from GitHub Releases via
Expand Down
3 changes: 2 additions & 1 deletion docs/astro.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export default defineConfig({
nebari({
nav: [
{ label: 'Docs', href: '/' },
{ label: 'Guides', href: '/guides/authoring-content/' },
{ label: 'Guides', href: '/guides/' },
{ label: 'Reference', href: '/reference/configuration/' },
],
}),
Expand All @@ -32,6 +32,7 @@ export default defineConfig({
{
label: 'Guides',
items: [
{ label: 'Overview', link: '/guides/' },
{
label: 'Authoring Content',
link: '/guides/authoring-content/',
Expand Down
23 changes: 23 additions & 0 deletions docs/e2e/screenshots.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { expect, test } from '@playwright/test';

test.describe('README screenshots', () => {
test.skip(
!process.env.SCREENSHOTS,
'set SCREENSHOTS=1 to regenerate README screenshots',
);

test('capture light and dark homepage screenshots', async ({ page }) => {
await page.setViewportSize({ width: 1440, height: 900 });
await page.goto('/');

for (const theme of ['light', 'dark'] as const) {
await page
.locator('html')
.evaluate((el, next) => el.setAttribute('data-theme', next), theme);
await expect(page.locator('.hero')).toBeVisible();
await page.screenshot({
path: `../.github/screenshots/${theme}.png`,
});
}
});
});
163 changes: 161 additions & 2 deletions docs/e2e/theme-search-a11y.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,9 @@ test('search returns the seeded token', async ({ page }) => {

// The probe token lives in the Components reference page; Pagefind surfaces it
// under that page's title.
await expect(page.locator('text=Components').first()).toBeVisible({
await expect(
page.locator('#starlight__search .pagefind-ui__result-link').first(),
).toBeVisible({
timeout: 15_000,
});
});
Expand Down Expand Up @@ -117,9 +119,11 @@ test('home and content pages have no serious/critical a11y violations', async ({
await page.setViewportSize({ width, height });
for (const path of [
'/',
'/guides/',
'/guides/authoring-content/',
'/reference/components/',
'/reference/kitchen-sink/',
'/this-page-does-not-exist/',
]) {
await page.goto(path);
await expect(page.locator('html')).toHaveAttribute(
Expand All @@ -132,8 +136,16 @@ test('home and content pages have no serious/critical a11y violations', async ({
el.scrollWidth <= el.clientWidth || el.hasAttribute('tabindex'),
),
);
const pageOverflow = await page.evaluate(() => ({
scroll: document.documentElement.scrollWidth,
client: document.documentElement.clientWidth,
}));
expect(
pageOverflow.scroll,
`${colorScheme} ${width}px ${path} overflow`,
).toBe(pageOverflow.client);
const results = await new AxeBuilder({ page })
.withTags(['wcag2a', 'wcag2aa'])
.withTags(['wcag2a', 'wcag2aa', 'wcag22aa'])
.analyze();
const serious = results.violations.filter(
(v) => v.impact === 'serious' || v.impact === 'critical',
Expand All @@ -149,6 +161,58 @@ test('home and content pages have no serious/critical a11y violations', async ({
}
});

// Nothing in the suite asserted computed type before this: build.test.ts greps class
// names and token mappings, and axe checks contrast. A 13px TOC entry where the design
// binds 14px therefore passed every gate. Size and weight only, never colour — Chromium
// returns colours in their authored space, as the note above explains.
const TYPE_SCALE: Array<{
selector: string;
fontSize?: string;
fontWeight?: string;
}> = [
{ selector: '#starlight__on-this-page', fontSize: '11px', fontWeight: '500' },
{ selector: '.right-sidebar starlight-toc a', fontSize: '14px' },
{
selector: '.right-sidebar starlight-toc a[aria-current="true"]',
fontWeight: '500',
},
{ selector: '.pagination-links a', fontSize: '12px', fontWeight: '500' },
{ selector: '.pagination-links .link-title', fontSize: '16px' },
{ selector: '.nbr-page-header h1', fontSize: '34px', fontWeight: '700' },
{ selector: '.nbr-page-meta', fontSize: '13px' },
{
selector: '.sidebar-content .group-label .large',
fontSize: '14px',
fontWeight: '400',
},
];

test('page chrome renders at the design type scale', async ({ page }) => {
await page.setViewportSize({ width: 1440, height: 900 });
await page.goto('/guides/deployment/build/');

// starlight-toc marks the current entry client-side on first intersection, so
// the aria-current row does not exist in the served HTML.
await expect(
page.locator('.right-sidebar starlight-toc a[aria-current="true"]'),
).toHaveCount(1);

for (const { selector, fontSize, fontWeight } of TYPE_SCALE) {
const target = page.locator(selector).first();
await expect(target, `${selector} is missing`).toBeAttached();
const actual = await target.evaluate((el) => {
const style = getComputedStyle(el);
return { fontSize: style.fontSize, fontWeight: style.fontWeight };
});
if (fontSize) {
expect(actual.fontSize, `${selector} font-size`).toBe(fontSize);
}
if (fontWeight) {
expect(actual.fontWeight, `${selector} font-weight`).toBe(fontWeight);
}
}
});

test('wide viewports centre the content panel when a TOC is present', async ({
page,
}) => {
Expand Down Expand Up @@ -262,3 +326,98 @@ test('exactly one "Site" nav landmark is exposed at each width', async ({
expect(exposed, `${width}px exposed ${exposed} "Site" navs`).toBe(1);
}
});

test('the TOC current entry tracks the last heading at the bottom of the page', async ({
page,
}) => {
await page.setViewportSize({ width: 1440, height: 900 });
await page.goto('/reference/components/');

const current = page.locator(
'.right-sidebar starlight-toc a[aria-current="true"]',
);
await expect(current).toHaveCount(1);

await page.evaluate(() => {
const el = document.scrollingElement ?? document.documentElement;
el.scrollTop = el.scrollHeight;
});
await expect(current).toHaveCount(1);
await expect(current).toHaveJSProperty('hash', '#guide-cards');

await page.locator('#card-grids-at-a-glance').evaluate((el) => {
const nav =
document.querySelector('header')?.getBoundingClientRect().height ?? 0;
window.scrollTo(
0,
el.getBoundingClientRect().top + window.scrollY - nav - 32,
);
});
await expect(current).toHaveCount(1);
await expect(current).not.toHaveJSProperty('hash', '#guide-cards');

await page.evaluate(() => {
const el = document.scrollingElement ?? document.documentElement;
el.scrollTop = el.scrollHeight;
});
await expect(current).toHaveJSProperty('hash', '#guide-cards');

await page.locator('#badge-variants').evaluate((el) => {
const nav =
document.querySelector('header')?.getBoundingClientRect().height ?? 0;
window.scrollTo(
0,
el.getBoundingClientRect().top + window.scrollY - nav - 32,
);
});
await expect(current).toHaveCount(1);

await page.evaluate(() => {
document
.querySelector('.right-sidebar-panel starlight-toc a[href="#full-docs"]')
?.setAttribute('aria-current', 'true');
});
await expect(current).toHaveCount(1);
});

test('a page that does not scroll does not force the last TOC entry', async ({
page,
}) => {
await page.setViewportSize({ width: 1440, height: 3000 });
await page.goto('/guides/deployment/build/');
const current = page.locator(
'.right-sidebar starlight-toc a[aria-current="true"]',
);
await expect(current).toHaveCount(1);
await expect(current).toHaveJSProperty('hash', '#_top');
});

test('the mobile TOC label tracks the last heading at the bottom of the page', async ({
page,
}) => {
await page.setViewportSize({ width: 375, height: 800 });
await page.goto('/reference/components/');
await page.evaluate(() => {
const el = document.scrollingElement ?? document.documentElement;
el.scrollTop = el.scrollHeight;
});
await expect(page.locator('.display-current')).toHaveText('Guide cards');
});

test('guide chips filter the list in place', async ({ page }) => {
await page.goto('/guides/');
const visible = page.locator('.nbr-guide-card:visible');
await expect(visible).toHaveCount(6);
const url = page.url();

await page.locator('label[for="gf-getting-started"]').click();
await expect(visible).toHaveCount(2);
expect(page.url()).toBe(url);

await page.locator('label[for="gf-deployment"]').click();
await expect(visible).toHaveCount(2);

await page.locator('label[for="gf-all"]').click();
await expect(visible).toHaveCount(6);
expect(page.url()).toBe(url);
});
3 changes: 2 additions & 1 deletion docs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@
"build": "bun run theme && astro build",
"build:base": "bun run theme && astro build --base /demo-pack",
"preview": "astro preview",
"e2e": "playwright test"
"e2e": "playwright test",
"screenshots": "SCREENSHOTS=1 playwright test e2e/screenshots.spec.ts"
},
"dependencies": {
"@astrojs/starlight": "^0.41.3",
Expand Down
19 changes: 19 additions & 0 deletions docs/src/content/docs/404.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
---
title: Page not found
description: That page does not exist.
template: splash
tableOfContents: false
hero:
tagline: The page you were looking for does not exist. It may have been moved or the link is out of date.
actions:
- text: Back to home
link: /
variant: primary
- text: Browse the guides
link: /guides/
variant: secondary
---

<div class="nbr-404">
Found a broken link? <a href="https://github.com/nebari-dev/starlight/issues">Report it on GitHub</a>.
</div>
18 changes: 18 additions & 0 deletions docs/src/content/docs/getting-started/configuration.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,24 @@ nebari({ logoHref: 'https://packs.nebari.dev/' });
See the [Configuration Options reference](/reference/configuration/) for the full
option table and defaults.

### `nav`

Top-level header tabs (Docs / Guides / Reference on this site). When omitted,
the header is byte-identical to stock Starlight. The active tab is the one
that shares the most leading path segments with the current URL (the root
tab is a fallback, not a prefix match). A tab can point at a leaf page and
still light for every page in that section:

```js
nebari({
nav: [
{ label: 'Docs', href: '/' },
{ label: 'Guides', href: '/guides/' },
{ label: 'Reference', href: '/reference/configuration/' },
],
});
```

## Starlight options worth setting

The theme leaves these to you — they're per-pack decisions.
Expand Down
Loading