← Homepage
Demo
DOCUMENTATION
/

stargazer

Component Explorer for Astro

Stargaze your components. Zero heavy deps, no throwaway pages. Just Astro.

Why Stargazer?

If you've ever built a component-based website (Astro, Next.js, Svelte, you name it), you've probably done this:

  1. Build a component
  2. Move on to the next one
  3. Three weeks later… "wait, what did that Hero look like again?"
  4. Create a random test page, import the component, check it, delete the page
  5. Repeat forever

We all do it. It works, but it's not great.

Stargazer started because I wanted something better. Just list your components in one config file and get a full visual catalog with viewport testing, zoom, and dark mode toggle. It runs inside your existing Astro dev server. No extra tools, no extra build step, no heavy install.

Inspired By

This was built on the ideas of some solid tools that already exist:

All great tools. But none of them are built for Astro, and most come with setup overhead that felt like overkill for what I needed.

Comparison

StorybookHistoireAstro Stargazer
Install size~50MB+~15MB~0.5MB
Config per component1 .stories file each1 .story file each1 file total
Extra dev serverYesYesNo, uses Astro's
Learning curveDecorators, args, controlsStory syntaxJust a list of paths
Astro-native

This isn't a Storybook replacement. If you need interactive controls, docs generation, or a huge addon ecosystem, Storybook is the right choice. Stargazer is for when you just want to see your components quickly.

Framework components work too. Astro supports React, Vue, Svelte, Solid and others via islands. If your .astro component imports a React button or a Vue card, Stargazer previews it exactly as it would render in your real page — hydration and all.

Requirements

RequirementVersion
Astro≥ 4.9.0
Node.js≥ 18.0.0

Framework components (React, Vue, Svelte, Solid…) work out of the box if you already have them set up in your Astro project — no extra config needed.


Install

Option A — astro add (recommended)

npx astro add astro-stargazer

This automatically updates your astro.config.mjs.

Option B — Manual

npm install astro-stargazer

Config Setup

1. Add to your Astro config

// astro.config.mjs
import { defineConfig } from 'astro/config';
import stargazer from 'astro-stargazer';

export default defineConfig({ 
  integrations: [stargazer()]
});

2. Create your config

Use defineConfig from astro-stargazer for full IDE autocomplete (zero runtime cost):

// stargazer.config.ts
import { defineConfig } from 'astro-stargazer';

export default defineConfig({ 
  mode: 'auto',
  defaultLayout: 'main',
  layouts: { main: './src/layouts/Layout.astro' },
});

Or run the setup wizard to generate it interactively:

npx create-stargazer

Or list components manually with mode: 'files':

import { defineConfig } from 'astro-stargazer';

export default defineConfig({ 
  mode: 'files',
  components: [
    { name: 'Hero', path: './src/components/Hero.astro', description: 'Landing hero section' },
    { name: 'Footer', path: './src/components/Footer.astro' },
  ],
});

That's it. No stories, no decorators, no extra syntax.

Run

npm run dev

Open /demo in your browser — check your terminal for the exact port (usually 4321). 🔭

Works on macOS, Linux and Windows — pure Node.js, no native binaries.


Configuration Reference

Create stargazer.config.ts (or .js / .mjs) in your project root:

import type { StargazerConfig } from 'astro-stargazer';

const config: StargazerConfig = { ... };
export default config;

Options

OptionTypeDefaultDescription
mode'files' | 'auto''auto'How components are discovered
excludestring[]['src/layouts','src/pages','src/styles']Paths to exclude (auto mode only)
defaultsRecord<string, unknown>{}Global props applied to all components
defaultLayoutstringundefinedKey from layouts map to use by default
layoutMapRecord<string, string>undefinedMap directory paths to layout keys (auto/files mode)
layoutsRecord<string, string>{}Map of layout names to file paths
componentsStargazerComponent[][]Components to preview (files mode only)
scanDirstring'./src'Directory to scan (auto mode only)
basestring'/demo'URL path for Stargazer
buildablebooleanfalseInclude in production builds (default: dev-only)
darkModefalse | DarkModeConfig{} (enabled)Dark mode toggle, set to false to hide

Component Entry

FieldTypeRequiredDescription
namestringDisplay name in the index
pathstringPath to .astro file (relative to project root)
descriptionstringShort description shown on the index card
layoutstringLayout key, overrides defaultLayout
propsRecord<string, unknown>Props, overrides defaults
variantsStargazerVariant[]Multiple prop combinations for the same component
categorystringCustom category label (default: detected from folder)

Variant Entry

FieldTypeRequiredDescription
namestringVariant display name
propsRecord<string, unknown>Props for this variant, overrides component-level props

Modes

Auto Mode (recommended for new projects)

Zero config. Stargazer recursively scans src/ and discovers all .astro files. Layouts, pages and styles are excluded by default. Component and layout discovery happens at runtime — create a new component, refresh the browser, and Stargazer picks it up instantly.

const config: StargazerConfig = { 
  mode: 'auto',
  defaultLayout: 'main',
  layouts: { main: './src/layouts/Layout.astro' },
};

Auto mode + multiple layouts (layoutMap)

If your project has more than one layout, use layoutMap to assign layouts by directory. More specific paths win.

const config: StargazerConfig = { 
  mode: 'auto',
  defaultLayout: 'main',
  layouts: { 
    main: './src/layouts/Layout.astro',
    blog: './src/layouts/BlogLayout.astro',
  },
  layoutMap: { 
    'src/components/blog': 'blog',
    'src/components': 'main',
  },
};

Auto mode + global defaults

Most projects have components that require common props like lang, theme, or a logged-in user object. Use defaults to pass them to every component automatically:

const config: StargazerConfig = { 
  mode: 'auto',
  defaults: { 
    lang: 'en',
    theme: 'default',
  },
  defaultLayout: 'main',
  layouts: { main: './src/layouts/Layout.astro' },
};

Important: In auto mode, components that require props but don't receive them will throw a runtime error in the preview. Add any required props to defaults to fix this.

Files Mode

All components listed in stargazer.config.ts:

const config: StargazerConfig = { 
  mode: 'files',
  components: [
    { name: 'Hero', path: './src/components/Hero.astro', description: 'Landing hero section' },
    { name: 'Footer', path: './src/components/Footer.astro' },
  ],
};

Best for: Full control over what's shown — choose exactly which components appear and in what order.


Recipes

Common configuration patterns for real projects.

Design system — buttons, badges, cards with all variants

If you're building a component library, list every variant explicitly so you can visually QA them all in one session:

const config: StargazerConfig = { 
  mode: 'files',
  defaultLayout: 'main',
  layouts: { main: './src/layouts/Layout.astro' },
  components: [
    { 
      name: 'Button',
      path: './src/components/ui/Button.astro',
      variants: [
        { name: 'Primary',   props: { variant: 'primary',   size: 'md' } },
        { name: 'Secondary', props: { variant: 'secondary', size: 'md' } },
        { name: 'Ghost',     props: { variant: 'ghost',     size: 'md' } },
        { name: 'Danger',    props: { variant: 'danger',    size: 'md' } },
        { name: 'Disabled',  props: { variant: 'primary',   disabled: true } },
        { name: 'Small',     props: { variant: 'primary',   size: 'sm' } },
        { name: 'Large',     props: { variant: 'primary',   size: 'lg' } },
      ],
    },
    { 
      name: 'Badge',
      path: './src/components/ui/Badge.astro',
      variants: [
        { name: 'Green',  props: { color: 'green',  label: 'Active'  } },
        { name: 'Red',    props: { color: 'red',    label: 'Error'   } },
        { name: 'Yellow', props: { color: 'yellow', label: 'Warning' } },
        { name: 'Blue',   props: { color: 'blue',   label: 'Info'    } },
      ],
    },
  ],
};

Multilingual site — preview every component in every locale

Use defaults for the fallback language and variants to preview other locales:

const config: StargazerConfig = { 
  mode: 'auto',
  defaults: { lang: 'en' },
  defaultLayout: 'main',
  layouts: { main: './src/layouts/Layout.astro' },
  components: [
    { 
      name: 'Hero',
      path: './src/components/Hero.astro',
      variants: [
        { name: 'English',    props: { lang: 'en' } },
        { name: 'Portuguese', props: { lang: 'pt' } },
        { name: 'Spanish',    props: { lang: 'es' } },
      ],
    },
    { 
      name: 'Pricing',
      path: './src/components/Pricing.astro',
      variants: [
        { name: 'USD', props: { currency: 'USD', lang: 'en' } },
        { name: 'EUR', props: { currency: 'EUR', lang: 'pt' } },
      ],
    },
  ],
};

Large project — auto mode with layout mapping by section

Use layoutMap when different sections of your site use different layouts:

const config: StargazerConfig = { 
  mode: 'auto',
  defaults: { lang: 'en', user: { name: 'Demo User', role: 'admin' } },
  defaultLayout: 'main',
  layouts: { 
    main:      './src/layouts/Layout.astro',
    blog:      './src/layouts/BlogLayout.astro',
    dashboard: './src/layouts/DashboardLayout.astro',
  },
  layoutMap: { 
    'src/components/dashboard': 'dashboard',
    'src/components/blog':      'blog',
    'src/components':           'main',
  },
  exclude: ['src/components/internal', 'src/components/deprecated'],
};

More specific paths win in layoutMapsrc/components/dashboard takes priority over src/components for any component inside it.


Variants

Variants let you preview the same component with different props — ideal for design system components like buttons, badges, and cards.

{ 
  name: 'Button',
  path: './src/components/Button.astro',
  variants: [
    { name: 'Primary',   props: { variant: 'primary',   size: 'md' } },
    { name: 'Secondary', props: { variant: 'secondary', size: 'md' } },
    { name: 'Ghost',     props: { variant: 'ghost',     size: 'md' } },
    { name: 'Danger',    props: { variant: 'danger',    size: 'md' } },
  ],
}

Each variant appears as a separate card in the index: Button / Primary, Button / Ghost, etc.

Example — Badge colors

{
  name: 'Badge',
  path: './src/components/Badge.astro',
  variants: [
    { name: 'Green',   props: { color: 'green',  label: 'Active'  } },
    { name: 'Red',     props: { color: 'red',    label: 'Error'   } },
    { name: 'Yellow',  props: { color: 'yellow', label: 'Warning' } },
    { name: 'Blue',    props: { color: 'blue',   label: 'Info'    } },
  ],
}

Props Inheritance

Props are merged in this order (later wins):

Global defaults → Component props → Variant props
const config: StargazerConfig = {
  defaults: { lang: 'en', colorMode: 'dark' },
  components: [
    {
      name: 'Hero',
      path: './src/components/Hero.astro',
      props: { colorMode: 'light' },         // overrides default
      variants: [
        { name: 'English' },                 // gets: { lang: 'en', colorMode: 'light' }
        { name: 'Portuguese', props: { lang: 'pt' } }, // gets: { lang: 'pt', colorMode: 'light' }
      ],
    },
  ],
};
PropGlobalComponentVariant (PT)Final
lang'en''pt''pt'
colorMode'dark''light''light'

Layout Wrapping

Components often need their parent layout for correct rendering — global CSS, navigation, fonts, etc.

const config: StargazerConfig = {
  defaultLayout: 'main',
  layouts: {
    main: './src/layouts/Layout.astro',
    blog: './src/layouts/BlogLayout.astro',
  },
  components: [
    { name: 'Hero',     path: './src/components/Hero.astro' },
    // ↑ uses 'main' layout (from defaultLayout)

    { name: 'Blog Card', path: './src/components/BlogCard.astro', layout: 'blog' },
    // ↑ uses 'blog' layout (override)

    { name: 'Icon',     path: './src/components/Icon.astro', layout: undefined },
    // ↑ no layout — renders standalone
  ],
};

The isStargazer Prop

Stargazer automatically injects { isStargazer: true } into the Layout's Astro.props. Use it to hide navbars and footers from previews:

---
// src/layouts/Layout.astro
const { isStargazer } = Astro.props;
---
<html lang="en">
  <body>
    {!isStargazer && <Header />}
    <slot />
    {!isStargazer && <Footer />}
  </body>
</html>

Override Editor & Settings

Each component card has a button (always visible, yellow) that opens the override editor modal. Changes are stored in localStorage — they never modify your config files.

Per-component override

Click on any card to:

Global Settings & Visibility

The ⚙ Settings button in the status bar applies overrides to every component in the session. Per-component overrides take priority:

component override  >  global override  >  config

Config default vs. session override: The defaultLayout in your stargazer.config.ts is the permanent baseline — what runs when nothing is overridden. The layout you pick in the ⚙ Settings modal is a temporary session override stored only in localStorage. Cards show Default Layout only when no override is active and the component is using its config default. If you select a layout in Settings (even the same one), it counts as an override and shows the layout filename instead.

Inside the Settings modal, you have access to Visibility Toggles:

CSS & Global Styles

SourceWorks?Why
Scoped <style> inside .astro✅ AlwaysCollected from Vite's module graph
Global CSS imported in a layout✅ With layoutParsed from the layout file
Global CSS not in a layoutOnly exists if the layout imports it

The fix: Always set a defaultLayout that imports your global stylesheet. Components without a layout show ⚠ no layout in the index.

How it works under the hood: When a component is requested for preview, Stargazer calls ssrLoadModule on the component file. This populates Vite's module graph with the component's scoped style modules (?astro&type=style). Stargazer then traverses the module graph to collect these URLs and injects them as <link> tags alongside the layout's global CSS imports.

Viewport & Zoom

Viewport Buttons

ButtonWidthUse case
FULL100%Current browser width
2560px2560px4K / Ultra-wide
1440px1440pxStandard desktop
1024px1024pxTablet landscape / Small laptop
780px780pxTablet portrait
390px390pxMobile

Viewport > screen width: Preview scales down visually but media queries respond to the real selected viewport width.

Zoom

Zoom appears only when viewport is ≤ 1024px:

ButtonScale
100%1.0
75%0.75
50%0.5
25%0.25 — full page at a glance

Dark Mode

The dark/light toggle is enabled by default. Configure to match your project:

// Disable entirely
darkMode: false

// Tailwind (class)
darkMode: { method: 'class' }

// DaisyUI / Pico CSS
darkMode: { method: 'data-theme' }

// Custom attribute (default behavior)
darkMode: { method: 'attribute', attribute: 'color-scheme' }

// Custom values
darkMode: { method: 'data-theme', dark: 'night', light: 'day' }

Full options

OptionTypeDefaultDescription
method'attribute' | 'class' | 'data-theme''attribute'How dark mode is applied
attributestring'color-scheme'Attribute name (attribute method only)
targetstring'html'CSS selector for the target element
darkstring'dark'Value/class for dark mode
lightstring'light'Value/class for light mode

The index shows a search input when more than 4 components are registered.

Category View All

Every category on the index page has a View All link. Clicking it opens a scrollable page showing every component in that category rendered at full size.

Keyboard Shortcuts

Available on any preview page:

KeyAction
FSwitch to FULL viewport
DSwitch to DARK theme
LSwitch to LIGHT theme
1Zoom 100%
2Zoom 75%
3Zoom 50% (only when viewport ≤ 1024px)
4Zoom 25% (only when viewport ≤ 1024px)
RReload the component iframe
CCopy Props to clipboard
Navigate to previous component
Navigate to next component
/Focus search input (index only)
EscapeClear search (index only)

Shortcuts are disabled when focus is inside a text input. Zoom shortcuts (14) are only active when viewport is ≤ 1024px — matching when the zoom buttons are visible.

Copy Props

The COPY PROPS button in the preview nav bar copies the current component's props as formatted JSON to your clipboard. Useful for sharing exact prop states with teammates or debugging.

URL State

Viewport and zoom are persisted in the URL query string:

/demo/hero?vp=390&zoom=0.75

Hot Reload

When you edit stargazer.config.ts, the browser reloads automatically — no need to restart the dev server.

Error Boundary

If a component fails to load or throws at runtime, Stargazer shows a styled error card instead of a blank page.

Fix the error in your component and click Reload.

Architecture

Your Astro Project
├── astro.config.mjs          ← adds stargazer() integration
├── stargazer.config.ts       ← your component registry
└── src/
    ├── components/           ← your components
    └── layouts/              ← your layouts

astro-stargazer (npm package)
├── src/
│   ├── integration.ts        ← Astro hooks, route injection, JSON API middleware
│   ├── scanner.ts            ← component discovery (all three modes)
│   ├── generator.ts          ← builds registry JSON
│   ├── types.ts              ← TypeScript interfaces
│   ├── routes/
│   │   ├── index.astro       ← component registry page with search
│   │   ├── preview.astro     ← single component view shell
│   │   ├── category.astro    ← category "View All" shell
│   │   ├── docs.astro        ← this documentation page
│   │   ├── frame/[slug].astro    ← renders a single component
│   │   ├── frame-cat/[...cat].astro ← renders all components in a category
│   │   └── _Nav.astro        ← shared nav bar component
│   └── client/
│       └── controls.js       ← viewport/zoom/theme/shortcuts
└── bin/
    └── create-stargazer.mjs  ← npx setup wizard

API Endpoints (Vite middleware)

EndpointDescription
/__stargazer/registry.jsonFull component list + config
/__stargazer/preview/:slugComponent metadata as JSON
/__stargazer/paths/:slugReturns /@fs/ URLs for component + layout + CSS
/__stargazer/render-category-meta/:catReturns JSON list of slugs for a category

Data flow

Single component view (/demo/[slug]):

  1. Integration reads stargazer.config.ts at dev server startup
  2. Scanner resolves components and merges props (defaults → component → variant)
  3. Route /demo/[slug] serves preview.astro — an HTML shell with the shared nav bar
  4. The shell renders an <iframe> pointing to /demo/frame/[slug]
  5. The frame route dynamically imports the Layout and Component, and renders them as a native Astro page
  6. CSS is collected from two sources: Layout file CSS imports and component scoped styles (via ssrLoadModule + module graph traversal)

Platform Compatibility

Astro Stargazer runs on macOS, Linux and Windows — any OS that supports Node.js ≥ 18.

Framework Compatibility

Stargazer previews .astro files. Since Astro natively supports React, Vue, Svelte, Solid, Preact, Alpine and others via its island architecture, any framework component imported inside an .astro file works automatically.

---
// src/components/Card.astro
import Button from './Button.tsx';    // React
import Tooltip from './Tooltip.vue';  // Vue
import Badge from './Badge.svelte';   // Svelte
---
<Button variant="primary" />
<Tooltip text="hello" client:hover />
<Badge color="green" />

Stargazer previews Card.astro and everything inside it renders exactly as it would in your real page — hydration directives included.


Contributing

Want to help? Contributions are welcome. Whether it's bug reports, feature ideas, or PRs for other framework support, feel free to jump in. Check the issues or just open one.

Roadmap

React, Vue, Svelte, Solid components work today — if you use them inside .astro files, Stargazer previews them with full hydration support.

The longer-term plan is standalone packages for projects that don't use Astro at all:

No timeline on these yet. Astro is the focus until v1.0 is solid.


Troubleshooting

Components not showing up

  1. Check that stargazer.config.ts exists in project root
  2. Verify path values start with ./ and are relative to project root
  3. Run npm run dev and check console for [astro-stargazer] Found X component(s)
  4. In mode: 'auto', make sure the component is inside src/ and not in an excluded path

Styles missing in preview

Layout not wrapping correctly

Stargazer not in production

By design — dev-only. Set buildable: true to include it in production builds.

Important: When using buildable: true, your Astro project must have a server adapter configured (Cloudflare, Vercel, Netlify, Node, etc.). Stargazer's routes are SSR-only.

Dark mode toggle not working

CSS must respond to the method you configured:

Search bar not showing up

The search input only appears when there are more than 4 components registered.

Copy Props button doesn't work

The Clipboard API requires either HTTPS or localhost. It won't work over plain HTTP on a network address.

Zoom buttons are not showing

Zoom only appears when the selected viewport is ≤ 1024px. At larger viewports there is no zoom.

Can I change the /demo URL?

Yes. Set the base option in your config:

const config: StargazerConfig = { 
  base: '/preview',
};

My site switched to hybrid output — will this break my build?

No. Stargazer temporarily switches output to hybrid in memory during astro dev so its SSR routes work. This change is never written to your config file and does not affect astro build.

CommandEffect
astro devStargazer active, output set to hybrid in memory only
astro buildStargazer skipped entirely — your output stays static
astro build + buildable: trueStargazer included, output set to hybrid, requires a server adapter

What adapter should I use with buildable: true?

Any official Astro adapter works: @astrojs/cloudflare, @astrojs/vercel, @astrojs/netlify, @astrojs/node. Configure it as you normally would — Stargazer does not add any adapter-specific code.

If your project currently uses output: 'static' with no adapter, the simplest option for staging is @astrojs/node in standalone mode. Or just leave buildable at its default false and use Stargazer in dev only.

Component shows an error page

The error boundary caught a failure. Read the error message shown in the iframe, fix the component, click Reload.

If the error says Cannot read properties of undefined, the component is trying to use a prop that wasn't passed. This is common in auto mode. Fix by adding the required prop to defaults:

const config: StargazerConfig = { 
  mode: 'auto',
  defaults: { 
    lang: 'en',   // fix: 'Cannot read properties of undefined (reading "contact")'
  },
};

Preview is blank but shows no error

The component probably renders no visible HTML — empty output, whitespace only, or a display: none element. Check the component itself and open the browser devtools inside the iframe to inspect.


MIT © Webxtek · Built with Astro