Skip to content

Launcher

The Launcher component provides a powerful command palette for keyboard-driven navigation in your Astro projects. Version 2.0 features an improved semantic structure with proper ARIA patterns: role="switch" for toggles and native anchors for navigation. The component includes full keyboard support via roving tabindex, instant search, and automatic integration with preference toggles from accessible-astro-components.

Use the Launcher component when you need to:

  • Provide keyboard-driven navigation
  • Create a command palette (like VS Code’s Cmd/Ctrl+K)
  • Allow quick searching through site pages
  • Toggle preferences (dark mode, high contrast, reduced motion)
  • Provide power users with efficient navigation
  • Add a search-style trigger in your header
  • Accessible by default: Semantic HTML with proper ARIA roles (role="switch" for toggles)
  • Keyboard navigation: Open with Cmd/Ctrl + K, navigate with arrow keys (roving tabindex)
  • Screen reader support: Live region announcements for results
  • Quick search: Instant client-side fuzzy search
  • Navigation links: Semantic <a> elements with custom icons
  • Switch toggles: LED-style indicators with role="switch"
  • Preference sync: Automatic sync with accessible-astro-components toggles
  • Dark mode: Automatic light/dark theming via light-dark() CSS
  • Customizable: Extensive styling through CSS custom properties
  • i18n ready: All text labels customizable via props
  • Zero dependencies: Pure Astro components
  • TypeScript: Full type support
Terminal window
npm install accessible-astro-launcher
---
import {
Launcher,
LauncherTrigger,
LauncherPreferences,
LauncherSwitch,
LauncherNav,
LauncherLink,
} from 'accessible-astro-launcher'
---
<LauncherTrigger launcherId="site-launcher" />
<Launcher id="site-launcher">
<LauncherPreferences label="Preferences">
<LauncherSwitch label="Dark mode" onAction="toggle-dark-mode" />
<LauncherSwitch label="High contrast" onAction="toggle-high-contrast" />
</LauncherPreferences>
<LauncherNav label="Navigation">
<LauncherLink href="/" label="Home" />
<LauncherLink href="/about" label="About" />
<LauncherLink href="/contact" label="Contact" />
</LauncherNav>
</Launcher>

The Launcher system consists of six components that work together:

The main dialog component containing the search input and results.

<Launcher
id="my-launcher"
labels={{
placeholder: "Type to search...",
noResults: "No results found",
close: "Close"
}}
>
<!-- LauncherPreferences and LauncherNav go here -->
</Launcher>

A button that opens the launcher. Can be placed anywhere in your layout.

<!-- Full trigger with placeholder -->
<LauncherTrigger launcherId="my-launcher" />
<!-- Compact trigger -->
<LauncherTrigger launcherId="my-launcher" compact />
<!-- Icon only trigger -->
<LauncherTrigger launcherId="my-launcher" iconOnly />
<!-- With gradient border effect -->
<LauncherTrigger launcherId="my-launcher" gradientBorder />

A <fieldset> wrapper with <legend> for grouping switch toggles.

<LauncherPreferences label="Preferences">
<LauncherSwitch label="Dark mode" onAction="toggle-dark-mode" />
<LauncherSwitch label="High contrast" onAction="toggle-high-contrast" />
</LauncherPreferences>

A toggle button with role="switch" and LED indicator for preference controls.

<LauncherSwitch
label="Dark mode"
onAction="toggle-dark-mode"
checked={false}
keywords={["theme", "night"]}
/>

A <nav> wrapper with heading for grouping navigation links.

<LauncherNav label="Pages" headingLevel={3}>
<LauncherLink href="/" label="Home" />
<LauncherLink href="/about" label="About" />
</LauncherNav>

A semantic anchor element for navigation within the launcher.

<!-- Basic link -->
<LauncherLink href="/dashboard" label="Dashboard" />
<!-- With search keywords -->
<LauncherLink
href="/dashboard"
label="Dashboard"
keywords={["admin", "panel"]}
/>
<!-- With custom icon -->
<LauncherLink href="/settings" label="Settings">
<svg slot="icon" viewBox="0 0 24 24"><!-- icon SVG --></svg>
</LauncherLink>
<!-- External link -->
<LauncherLink
href="https://github.com"
label="GitHub"
target="_blank"
rel="noopener noreferrer"
/>
PropTypeDefaultDescription
idstringRequiredUnique identifier (must match launcherId on triggers)
labelsLauncherLabels{}i18n labels object for all UI text
classstring''Additional CSS classes
PropertyTypeDefaultDescription
placeholderstring'Type to search...'Search input placeholder
noResultsstring'No results for "{query}"'Text shown when no results (use {query} placeholder)
endOfResultsstring'End of results'Text shown at end of results
resultsCountstring'{count} results for "{query}"'Screen reader announcement (use {count} and {query})
closestring'Close'Close button label
clearstring'Clear'Clear button label
toSelectstring'Select'Hint text for Enter key
toNavigatestring'Navigate'Hint text for arrow keys
toClosestring'Close'Hint text for Escape key
searchHintstring'Type to filter...'Screen reader hint for search
PropTypeDefaultDescription
launcherIdstringRequiredID of the launcher to open
idstringundefinedOptional trigger element ID
placeholderstring'Search'Placeholder text
shortcutKeystring'K'Keyboard shortcut key to display
compactbooleanfalseCompact mode without placeholder
iconOnlybooleanfalseIcon-only mode
gradientBorderbooleanfalseAnimated gradient border effect
classstring''Additional CSS classes
PropTypeDefaultDescription
labelstringRequiredFieldset legend text
classstring''Additional CSS classes
PropTypeDefaultDescription
labelstringRequiredDisplay text
onActionstringRequiredAction identifier
checkedbooleanfalseInitial checked state
keywordsstring[][]Additional search keywords
typeLabelstring'Toggle'Label for type indicator
classstring''Additional CSS classes
PropTypeDefaultDescription
labelstringRequiredNavigation heading text
headingLevel2 | 3 | 4 | 5 | 63Heading level for label
classstring''Additional CSS classes
PropTypeDefaultDescription
labelstringRequiredDisplay text
hrefstringRequiredURL to navigate to
keywordsstring[][]Additional search keywords
typeLabelstring'Go to'Label for type indicator
classstring''Additional CSS classes

Slots:

SlotDescription
iconCustom icon for navigation links

Accessibility isn’t an afterthought - it’s built into the core of this component with proper semantic HTML and ARIA patterns.

KeyAction
Cmd/Ctrl + KOpen launcher
Arrow Up/DownNavigate items
EnterSelect item
EscapeClose launcher
TabMove between header elements

You can type at any time while navigating - the caret stays in the search input, so there’s no need to refocus.

Version 2.0 uses semantic HTML elements with appropriate ARIA:

  • Dialog: <dialog> with aria-modal="true" and aria-labelledby
  • Search input: <input type="search"> with aria-controls and aria-activedescendant
  • Preferences: <fieldset> with <legend> for grouping
  • Switches: <button role="switch"> with aria-checked for state
  • Navigation: <nav> with aria-labelledby heading
  • Links: Native <a> elements within <ul>/<li> structure
  • Live region: role="status" for results count announcements
  • Focus stays on search input at all times (enabling continuous typing)
  • aria-activedescendant manages the active item for screen readers
  • Arrow keys update the active item without moving DOM focus
  • Focus returns to trigger element when launcher closes
  • Clear visual indicators on the active item
  • LED-style indicators for switch toggle states
  • Hover/focus highlighting on all items
  • Visible focus outlines
  • Proper color contrast in light and dark modes

The Launcher component dispatches custom events that you can listen for:

Dispatched when a switch item is activated.

document.addEventListener('launcher:action', (event) => {
switch (event.detail.action) {
case 'toggle-dark-mode':
window.darkMode?.toggle()
break
case 'toggle-high-contrast':
window.highContrast?.toggle()
break
case 'toggle-reduced-motion':
window.reducedMotion?.toggle()
break
case 'logout':
window.location.href = '/logout'
break
}
})

Dispatched when the launcher opens. Useful for syncing preference states.

document.addEventListener('launcher:open', () => {
// Sync preference states when launcher opens
console.log('Launcher opened')
})

Make the Launcher your own while maintaining its accessibility features.

The Launcher uses CSS custom properties for easy theming:

:root {
/* Base theme colors */
--launcher-theme-light: #fff;
--launcher-theme-dark: #090b0f;
/* These are auto-generated from theme colors */
--launcher-text-color: /* auto light/dark */;
--launcher-subtle-text-color: /* auto light/dark */;
--launcher-outer-border-color: /* auto light/dark */;
--launcher-inner-border-color: /* auto light/dark */;
--launcher-main-body-color: /* auto light/dark */;
--launcher-action-bar-color: /* auto light/dark */;
--launcher-kbd-color: /* auto light/dark */;
--launcher-interaction-color: /* auto light/dark */;
--launcher-backdrop-color: rgba(0 0 0 / 0.3);
}
/* Custom purple theme */
:root {
--launcher-theme-light: #faf5ff;
--launcher-theme-dark: #1a0a2e;
}
/* Larger launcher */
:root {
--launcher-width: min(95vw, 800px);
--launcher-height: min(70vh, 600px);
}

Here’s a full implementation showing all features:

---
import {
Launcher,
LauncherTrigger,
LauncherPreferences,
LauncherSwitch,
LauncherNav,
LauncherLink,
} from 'accessible-astro-launcher'
import { DarkMode, HighContrast, ReducedMotion } from 'accessible-astro-components'
---
<header>
<nav>
<a href="/">My Site</a>
<LauncherTrigger launcherId="main-launcher" gradientBorder />
<DarkMode />
</nav>
</header>
<Launcher
id="main-launcher"
labels={{
placeholder: "Search pages, actions...",
noResults: "Nothing found. Try a different search.",
}}
>
<LauncherPreferences label="Preferences">
<LauncherSwitch label="Toggle dark mode" onAction="toggle-dark-mode" />
<LauncherSwitch label="Toggle high contrast" onAction="toggle-high-contrast" />
<LauncherSwitch label="Toggle reduced motion" onAction="toggle-reduced-motion" />
</LauncherPreferences>
<LauncherNav label="Pages">
<LauncherLink href="/" label="Home" keywords={["start", "main"]} />
<LauncherLink href="/about" label="About Us" keywords={["team", "company"]} />
<LauncherLink href="/blog" label="Blog" keywords={["articles", "posts"]} />
<LauncherLink href="/contact" label="Contact" keywords={["email", "form"]} />
</LauncherNav>
<LauncherNav label="Account">
<LauncherLink href="/settings" label="Settings" />
</LauncherNav>
</Launcher>
<!-- Handle custom actions -->
<script>
document.addEventListener('launcher:action', (event) => {
switch (event.detail.action) {
case 'toggle-dark-mode':
window.darkMode?.toggle()
break
case 'toggle-high-contrast':
window.highContrast?.toggle()
break
case 'toggle-reduced-motion':
window.reducedMotion?.toggle()
break
}
})
</script>

The package includes full TypeScript definitions. Import types as needed:

import type {
LauncherProps,
LauncherTriggerProps,
LauncherPreferencesProps,
LauncherSwitchProps,
LauncherNavProps,
LauncherLinkProps,
LauncherLabels,
LauncherActionEventDetail,
} from 'accessible-astro-launcher'

Integration with accessible-astro-components

Section titled “Integration with accessible-astro-components”

The Launcher automatically syncs with preference toggles from accessible-astro-components:

EventAction ID
darkmode:changetoggle-dark-mode
highcontrast:changetoggle-high-contrast
reducemotion:changetoggle-reduced-motion

When you use these action IDs, the launcher automatically:

  • Updates the LED indicator state when preferences change
  • Listens for preference change events
  • Syncs state when the launcher opens
v1 Componentv2 ComponentNotes
LauncherListRemovedNo longer needed
LauncherGroupLauncherPreferences or LauncherNavSplit by semantic purpose
LauncherItem type="action"LauncherSwitchDedicated switch component
LauncherItem type="navigation"LauncherLinkDedicated link component
v1 Propv2 PropNotes
LauncherItem.typeRemovedComponent determines type
LauncherItem.pressedLauncherSwitch.checkedRenamed for clarity
LauncherItem.onActionLauncherSwitch.onActionSame on switch
LauncherItem.hrefLauncherLink.hrefSame on link
  1. Update imports

    import { Launcher, LauncherTrigger, LauncherList, LauncherGroup, LauncherItem } from 'accessible-astro-launcher'
    import { Launcher, LauncherTrigger, LauncherPreferences, LauncherSwitch, LauncherNav, LauncherLink } from 'accessible-astro-launcher'
  2. Remove LauncherList wrapper

    <Launcher id="my-launcher">
    <LauncherList>
    <!-- content -->
    </LauncherList>
    </Launcher>
  3. Replace action items with LauncherSwitch

    <LauncherGroup label="Preferences">
    <LauncherItem type="action" label="Dark mode" onAction="toggle-dark-mode" pressed={false} />
    </LauncherGroup>
    <LauncherPreferences label="Preferences">
    <LauncherSwitch label="Dark mode" onAction="toggle-dark-mode" checked={false} />
    </LauncherPreferences>
  4. Replace navigation items with LauncherLink

    <LauncherGroup label="Pages">
    <LauncherItem type="navigation" label="Home" href="/" />
    <LauncherItem type="navigation" label="About" href="/about" />
    </LauncherGroup>
    <LauncherNav label="Pages">
    <LauncherLink label="Home" href="/" />
    <LauncherLink label="About" href="/about" />
    </LauncherNav>
  5. Update custom icons

    <LauncherItem type="navigation" label="Settings" href="/settings">
    <LauncherLink label="Settings" href="/settings">
    <svg slot="icon"><!-- icon --></svg>
    </LauncherItem>
    </LauncherLink>

v1 (Before):

<Launcher id="site-launcher">
<LauncherList>
<LauncherGroup label="Preferences">
<LauncherItem type="action" label="Dark mode" onAction="toggle-dark-mode" />
</LauncherGroup>
<LauncherGroup label="Navigate">
<LauncherItem type="navigation" label="Home" href="/" />
<LauncherItem type="navigation" label="About" href="/about" />
</LauncherGroup>
</LauncherList>
</Launcher>

v2 (After):

<Launcher id="site-launcher">
<LauncherPreferences label="Preferences">
<LauncherSwitch label="Dark mode" onAction="toggle-dark-mode" />
</LauncherPreferences>
<LauncherNav label="Navigate">
<LauncherLink label="Home" href="/" />
<LauncherLink label="About" href="/about" />
</LauncherNav>
</Launcher>

Version 2.0 introduces these changes to improve semantic correctness:

  1. Better screen reader announcements: Switches are now announced as “switch, on/off” instead of “option”
  2. Proper ARIA patterns: Uses role="switch" for toggles, which is the correct pattern for on/off controls
  3. Native elements: Links are now native <a> elements, improving screen reader navigation
  4. Semantic grouping: <fieldset> for switches and <nav> for links provide better structure
  5. Clearer API: Component names directly indicate their purpose