---
title: Launcher
description: A keyboard-first, CoreBox-style launcher with a live preview.
category: TemplateApp
status: beta
since: 0.6.0
tags: [template, launcher, search, keyboard, corebox]
syncStatus: reviewed
verified: false
---

## Usage

### CoreBox-style launcher
:::TuffDemoWrapper{demo="TemplateLauncherDemo" code-lang="vue" description="Results as you type; ↑ ↓ move the highlight, ↵ runs it, ⌘K opens actions."}
---
code: |
  <script setup lang="ts">
  import { computed, ref, useId } from 'vue'
  import TemplateFrame from './TemplateFrame.vue'

  const query = ref('')
  const scope = ref('all')
  const activeIndex = ref(0)
  const results = computed(() => search(query.value, scope.value))
  const active = computed(() => results.value[activeIndex.value])
  const listId = useId()

  // Every key lives on the field: focus never leaves it, and aria-activedescendant points at the highlight
  function onKeydown(event: KeyboardEvent) {
    if (event.key === 'ArrowDown' || event.key === 'ArrowUp') {
      event.preventDefault()
      move(event.key === 'ArrowDown' ? 1 : -1)
    }
    else if (event.key === 'Enter') {
      event.preventDefault()
      run(active.value)
    }
    else if (event.key === 'Escape' && query.value) {
      event.preventDefault()
      query.value = ''
    }
    else if (event.key === 'Escape' && scope.value !== 'all') {
      event.preventDefault()
      scope.value = 'all'
    }
  }
  </script>

  <template>
    <TemplateFrame title="Launcher" @enter="playTyping">
      <div class="launcher">
        <TxGlassSurface width="100%" height="100%" :border-radius="18">
          <TxSearchInput
            v-model="query"
            role="combobox"
            aria-expanded="true"
            :aria-controls="listId"
            :aria-activedescendant="`${listId}-${activeIndex}`"
            @keydown="onKeydown"
          />
          <TxFilterChips v-model="scope" :items="scopes" />
          <div :id="listId" role="listbox">
            <div
              v-for="(item, index) in results"
              :id="`${listId}-${index}`"
              :key="item.id"
              role="option"
              :aria-selected="index === activeIndex"
            >
              <TxIconChip :size="30" :tone="item.tone" variant="soft">
                <i :class="item.icon" />
              </TxIconChip>
              {{ item.title }}
              <TxTag :label="item.kind" size="sm" variant="soft" />
              <TxKbd v-if="index < 5">⌘{{ index + 1 }}</TxKbd>
            </div>
          </div>
          <TxMarkdownView :content="active?.preview ?? tips" />
          <footer>
            <TxDropdownMenu v-model="actionsOpen" placement="top-end">
              <!-- Open, Reveal in Finder, Pin to suggestions, Disable plugin -->
            </TxDropdownMenu>
            <TxStatusBadge text="Indexing 82%" status="info" size="sm" />
          </footer>
        </TxGlassSurface>
      </div>
    </TemplateFrame>
  </template>
---
:::

## Overview

A launcher modeled on Tuff's CoreBox: one field searches every type, the highlighted result is previewed, and nothing needs the mouse.

- Search covers apps, plugins, files, the clipboard, AI, and system actions, with scope counts that follow the query; a sum such as `128*4` is answered in the first row.
- ↑ ↓ move the highlight and the preview follows; ↵ runs it; ⌘K opens the result's actions, and a pinned item moves to the top of the suggestions.
- Esc works in two steps: it clears the query, then resets the scope to All.
- With no results, Ask Tuff Intelligence turns the query into an AI prompt.
- It plays once in view without focusing the field; typing or clicking stops it, and reduced motion shows the final state.
- ⌘1–⌘5 are hints only, since the browser reserves ⌘ plus a digit; wide (≥ 960px) shows the Dock, and narrow (< 640px) drops the preview.

## Customization

- For real data, replace `CATALOG` with your index's response and keep `kind`, `tone`, and `keywords`; `scoreEntry()` scores a title prefix 3, a title match 2, and a keyword or subtitle hit 1.
- It skips TxSearchPanel, which neither reports the highlight nor exposes its input; for an inline search without a preview, use TxSearchPanel.
- To summon it globally, put the window in a TxModal or your own Teleport overlay; bind the summon shortcut on the host's root, not `window`.
- Keep links out of preview markdown, since TxMarkdownView renders real `<a>` elements; in a desktop app, intercept the click and let the host open it.

## Related components

| Region | Components | Role |
|---|---|---|
| Desktop | [TxIconChip](/docs/dev/components/icon-chip), a hand-drawn wallpaper and menu bar | Wallpaper; a Dock when wide |
| Window | [TxGlassSurface](/docs/dev/components/glass-surface) | Refracting glass window |
| Input | [TxSearchInput](/docs/dev/components/search-input), [TxKbd](/docs/dev/components/kbd) | Handles arrow keys, ↵, and Esc |
| Scope | [TxFilterChips](/docs/dev/components/filter-chips) | Scopes with hit counts |
| Results | A hand-built listbox, [TxIconChip](/docs/dev/components/icon-chip), [TxTag](/docs/dev/components/tag), [TxKbd](/docs/dev/components/kbd), [TxSearchEmpty](/docs/dev/components/search-empty) | Ranked matches, hints, and empty state |
| Preview | [TxMarkdownView](/docs/dev/components/markdown-view) | Details that follow the highlight |
| Footer | [TxDropdownMenu](/docs/dev/components/dropdown-menu), [TxStatusBadge](/docs/dev/components/status-badge) | Feedback, actions menu, indexing progress |
