App Launcher
The Apps popout, with search, a recents strip and the apps installed in a server.
The Apps popout, with search, a recents strip and the apps installed in a server. Compose it from the exported registry parts so local content and behaviour stay in the application while the visual contract stays token-driven.
import {
AppLauncher,
AppLauncherBody,
AppLauncherHandle,
AppLauncherItem,
AppLauncherItemIcon,
AppLauncherList,
AppLauncherRecent,
AppLauncherRecents,
AppLauncherSearch,
AppLauncherSearchInput,
AppLauncherSection,
AppLauncherSectionHeader,
AppLauncherSectionTitle,
AppLauncherViewMore,
} from "@/registry/bases/newt/ui/app-launcher"
const ShieldIcon = () => (
<svg viewBox="0 0 24 24" fill="currentColor">
<path d="M12 2 4 5v7c0 5 3.4 8.4 8 10 4.6-1.6 8-5 8-10V5l-8-3Z" />
</svg>
)
const BoardIcon = () => (
<svg viewBox="0 0 24 24" fill="currentColor">
<path d="M4 4h16v12H8l-4 4V4Z" />
</svg>
)
export default function AppLauncherDemo() {
return (
<AppLauncher aria-label="Apps" className="h-[420px]">
<AppLauncherHandle />
<AppLauncherSearch>
<AppLauncherSearchInput
placeholder="Search Apps & Commands"
aria-label="Search apps and commands"
/>
</AppLauncherSearch>
<AppLauncherBody>
<AppLauncherSection aria-label="Recents">
<AppLauncherSectionHeader>
<AppLauncherSectionTitle>Recents</AppLauncherSectionTitle>
<AppLauncherViewMore>View More</AppLauncherViewMore>
</AppLauncherSectionHeader>
<AppLauncherRecents>
<AppLauncherRecent aria-label="Moderation">
<ShieldIcon />
</AppLauncherRecent>
<AppLauncherRecent aria-label="Poll Night">
<BoardIcon />
</AppLauncherRecent>
</AppLauncherRecents>
</AppLauncherSection>
<AppLauncherSection aria-label="Apps in this server">
<AppLauncherSectionHeader>
<AppLauncherSectionTitle>
Apps in this Server
</AppLauncherSectionTitle>
<AppLauncherViewMore>View More</AppLauncherViewMore>
</AppLauncherSectionHeader>
<AppLauncherList>
<AppLauncherItem
name="Moderation"
description="Bans, timeouts and audit logs."
icon={
<AppLauncherItemIcon round>
<ShieldIcon />
</AppLauncherItemIcon>
}
/>
<AppLauncherItem
promoted
name="Poll Night"
description="Run a quick poll in any channel."
icon={
<AppLauncherItemIcon>
<BoardIcon />
</AppLauncherItemIcon>
}
/>
</AppLauncherList>
</AppLauncherSection>
</AppLauncherBody>
</AppLauncher>
)
}<script setup lang="ts">
import {
AppLauncher,
AppLauncherBody,
AppLauncherHandle,
AppLauncherItem,
AppLauncherItemIcon,
AppLauncherList,
AppLauncherRecent,
AppLauncherRecents,
AppLauncherSearch,
AppLauncherSearchInput,
AppLauncherSection,
AppLauncherSectionHeader,
AppLauncherSectionTitle,
AppLauncherViewMore,
} from "~~/registry/bases/newt/ui/app-launcher"
const shieldPath = "M12 2 4 5v7c0 5 3.4 8.4 8 10 4.6-1.6 8-5 8-10V5l-8-3Z"
const boardPath = "M4 4h16v12H8l-4 4V4Z"
</script>
<template>
<AppLauncher aria-label="Apps" class="h-[420px]">
<AppLauncherHandle />
<AppLauncherSearch>
<AppLauncherSearchInput
placeholder="Search Apps & Commands"
aria-label="Search apps and commands"
/>
</AppLauncherSearch>
<AppLauncherBody>
<AppLauncherSection aria-label="Recents">
<AppLauncherSectionHeader>
<AppLauncherSectionTitle>Recents</AppLauncherSectionTitle>
<AppLauncherViewMore>View More</AppLauncherViewMore>
</AppLauncherSectionHeader>
<AppLauncherRecents>
<AppLauncherRecent aria-label="Moderation">
<svg viewBox="0 0 24 24" fill="currentColor">
<path :d="shieldPath" />
</svg>
</AppLauncherRecent>
<AppLauncherRecent aria-label="Poll Night">
<svg viewBox="0 0 24 24" fill="currentColor">
<path :d="boardPath" />
</svg>
</AppLauncherRecent>
</AppLauncherRecents>
</AppLauncherSection>
<AppLauncherSection aria-label="Apps in this server">
<AppLauncherSectionHeader>
<AppLauncherSectionTitle>Apps in this Server</AppLauncherSectionTitle>
<AppLauncherViewMore>View More</AppLauncherViewMore>
</AppLauncherSectionHeader>
<AppLauncherList>
<AppLauncherItem
name="Moderation"
description="Bans, timeouts and audit logs."
>
<template #icon>
<AppLauncherItemIcon round>
<svg viewBox="0 0 24 24" fill="currentColor">
<path :d="shieldPath" />
</svg>
</AppLauncherItemIcon>
</template>
</AppLauncherItem>
<AppLauncherItem
promoted
name="Poll Night"
description="Run a quick poll in any channel."
>
<template #icon>
<AppLauncherItemIcon>
<svg viewBox="0 0 24 24" fill="currentColor">
<path :d="boardPath" />
</svg>
</AppLauncherItemIcon>
</template>
</AppLauncherItem>
</AppLauncherList>
</AppLauncherSection>
</AppLauncherBody>
</AppLauncher>
</template>Installation
pnpm dlx newtui@latest add app-launchernpx newtui@latest add app-launcheryarn dlx newtui@latest add app-launcherbunx newtui@latest add app-launcherCopy the source into your project
import * as React from "react"
import { cn } from "@/lib/utils"
/* The Apps popout: search, recents, and the apps installed in a server. */
const AppLauncher = React.forwardRef<
HTMLDivElement,
React.ComponentProps<"div">
>(({ className, role = "dialog", ...props }, ref) => (
<div
ref={ref}
role={role}
className={cn(
"flex h-[min(680px,calc(100dvh-2rem))] w-full max-w-[504px] flex-col overflow-hidden rounded-md border border-newt-border bg-newt-bg-surface text-newt-text-primary shadow-elevation-high",
className
)}
{...props}
/>
))
AppLauncher.displayName = "AppLauncher"
/* Grip shown when the launcher is presented as a bottom sheet. */
const AppLauncherHandle = React.forwardRef<
HTMLDivElement,
React.ComponentProps<"div">
>(({ className, ...props }, ref) => (
<div
ref={ref}
aria-hidden="true"
className={cn(
"mx-auto mt-2 h-1 w-9 shrink-0 rounded-full bg-newt-border",
className
)}
{...props}
/>
))
AppLauncherHandle.displayName = "AppLauncherHandle"
/* Drill-in header: back control on the left, view title beside it. */
const AppLauncherHeader = React.forwardRef<
HTMLDivElement,
React.ComponentProps<"div">
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn(
"flex h-14 shrink-0 items-center gap-2 px-2 text-base font-semibold",
className
)}
{...props}
/>
))
AppLauncherHeader.displayName = "AppLauncherHeader"
const AppLauncherBack = React.forwardRef<
HTMLButtonElement,
React.ComponentProps<"button">
>(({ className, type = "button", ...props }, ref) => (
<button
ref={ref}
type={type}
className={cn(
"inline-flex h-8 w-8 shrink-0 cursor-pointer items-center justify-center rounded-full border-0 bg-newt-bg-elevated p-0 text-newt-text-secondary transition-colors duration-fast ease-newt",
"hover:bg-newt-bg-hover hover:text-newt-text-primary",
"focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-newt-text-link",
className
)}
{...props}
/>
))
AppLauncherBack.displayName = "AppLauncherBack"
const SearchIcon = () => (
<svg
viewBox="0 0 20 20"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
aria-hidden="true"
className="pointer-events-none absolute start-3.5 top-1/2 h-5 w-5 -translate-y-1/2 text-newt-text-muted"
>
<circle cx="9" cy="9" r="5.5" />
<path d="M13 13l4 4" />
</svg>
)
/* Search field wrapper; place an `<AppLauncherSearchInput>` inside it. */
const AppLauncherSearch = React.forwardRef<
HTMLDivElement,
React.ComponentProps<"div">
>(({ className, children, ...props }, ref) => (
<div
ref={ref}
className={cn("shrink-0 px-4 pb-3 pt-4", className)}
{...props}
>
<div className="relative flex items-center">
<SearchIcon />
{children}
</div>
</div>
))
AppLauncherSearch.displayName = "AppLauncherSearch"
const AppLauncherSearchInput = React.forwardRef<
HTMLInputElement,
React.ComponentProps<"input">
>(({ className, type = "search", ...props }, ref) => (
<input
ref={ref}
type={type}
autoComplete="off"
spellCheck={false}
className={cn(
"h-11 w-full appearance-none rounded-md border-2 border-transparent bg-newt-bg-input py-0 ps-11 pe-3 text-base text-newt-text-primary outline-none",
"placeholder:text-newt-text-muted focus:border-newt-brand",
className
)}
{...props}
/>
))
AppLauncherSearchInput.displayName = "AppLauncherSearchInput"
const AppLauncherBody = React.forwardRef<
HTMLDivElement,
React.ComponentProps<"div">
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn(
"flex min-h-0 flex-1 flex-col gap-6 overflow-y-auto px-4 pb-4",
className
)}
{...props}
/>
))
AppLauncherBody.displayName = "AppLauncherBody"
const AppLauncherSection = React.forwardRef<
HTMLElement,
React.ComponentProps<"section">
>(({ className, ...props }, ref) => (
<section
ref={ref}
className={cn("flex flex-col gap-2", className)}
{...props}
/>
))
AppLauncherSection.displayName = "AppLauncherSection"
const AppLauncherSectionHeader = React.forwardRef<
HTMLDivElement,
React.ComponentProps<"div">
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn("flex items-center justify-between gap-2 px-0.5", className)}
{...props}
/>
))
AppLauncherSectionHeader.displayName = "AppLauncherSectionHeader"
const AppLauncherSectionTitle = React.forwardRef<
HTMLHeadingElement,
React.ComponentProps<"h2">
>(({ className, ...props }, ref) => (
<h2
ref={ref}
className={cn(
"m-0 inline-flex items-center gap-1 text-base font-medium text-newt-text-primary",
className
)}
{...props}
/>
))
AppLauncherSectionTitle.displayName = "AppLauncherSectionTitle"
const AppLauncherViewMore = React.forwardRef<
HTMLButtonElement,
React.ComponentProps<"button">
>(({ className, type = "button", ...props }, ref) => (
<button
ref={ref}
type={type}
className={cn(
"cursor-pointer rounded-sm border-0 bg-transparent p-0 text-base font-semibold text-newt-text-link no-underline hover:underline",
"focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-newt-text-link",
className
)}
{...props}
/>
))
AppLauncherViewMore.displayName = "AppLauncherViewMore"
/* Card that stacks `<AppLauncherItem>` rows behind one rounded surface. */
const AppLauncherList = React.forwardRef<
HTMLDivElement,
React.ComponentProps<"div">
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn(
"flex flex-col overflow-hidden rounded-md bg-newt-bg-elevated",
"[&>*+*]:border-t [&>*+*]:border-newt-border",
className
)}
{...props}
/>
))
AppLauncherList.displayName = "AppLauncherList"
export interface AppLauncherItemProps extends Omit<
React.ComponentProps<"button">,
"children"
> {
name: string
description?: string
/** Icon for the app; pair with `<AppLauncherItemIcon>`. */
icon?: React.ReactNode
/** Marks a paid placement, as the client does for promoted apps. */
promoted?: boolean
}
const AppLauncherItem = React.forwardRef<
HTMLButtonElement,
AppLauncherItemProps
>(
(
{
className,
name,
description,
icon,
promoted = false,
type = "button",
...props
},
ref
) => (
<button
ref={ref}
type={type}
className={cn(
"flex w-full cursor-pointer items-center gap-3 border-0 bg-transparent px-3 py-3 text-start transition-colors duration-fast ease-newt",
"hover:bg-newt-bg-hover",
"focus-visible:outline focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-newt-text-link",
className
)}
{...props}
>
{icon}
<span className="flex min-w-0 flex-1 flex-col gap-0.5">
<span className="flex min-w-0 items-center gap-1.5">
<span className="truncate text-base font-medium leading-tight text-newt-text-primary">
{name}
</span>
{promoted ? (
<span className="shrink-0 rounded-full bg-newt-text-primary px-1.5 py-0.5 text-[10px] font-bold uppercase leading-none tracking-wide text-newt-bg-surface">
Promoted
</span>
) : null}
</span>
{description ? (
<span className="truncate text-sm leading-snug text-newt-text-muted">
{description}
</span>
) : null}
</span>
</button>
)
)
AppLauncherItem.displayName = "AppLauncherItem"
export interface AppLauncherItemIconProps extends React.ComponentProps<"span"> {
/** Bots use circular avatars; games and activities keep the squircle. */
round?: boolean
}
const AppLauncherItemIcon = React.forwardRef<
HTMLSpanElement,
AppLauncherItemIconProps
>(({ className, round = false, ...props }, ref) => (
<span
ref={ref}
aria-hidden="true"
className={cn(
"inline-flex h-10 w-10 shrink-0 items-center justify-center overflow-hidden bg-newt-bg-base text-newt-text-secondary",
round ? "rounded-full" : "rounded-[10px]",
"[&>img]:h-full [&>img]:w-full [&>img]:object-cover [&>svg]:h-5 [&>svg]:w-5",
className
)}
{...props}
/>
))
AppLauncherItemIcon.displayName = "AppLauncherItemIcon"
/* Horizontal strip of recently used apps. */
const AppLauncherRecents = React.forwardRef<
HTMLDivElement,
React.ComponentProps<"div">
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn(
"flex gap-3 overflow-x-auto [scrollbar-width:none] [&::-webkit-scrollbar]:hidden",
className
)}
{...props}
/>
))
AppLauncherRecents.displayName = "AppLauncherRecents"
const AppLauncherRecent = React.forwardRef<
HTMLButtonElement,
React.ComponentProps<"button">
>(({ className, type = "button", ...props }, ref) => (
<button
ref={ref}
type={type}
className={cn(
"inline-flex h-12 w-12 shrink-0 cursor-pointer items-center justify-center overflow-hidden rounded-[14px] border-0 bg-newt-bg-base p-0 text-newt-text-secondary transition-[filter] duration-fast ease-newt",
"hover:brightness-110",
"focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-newt-text-link",
"[&>img]:h-full [&>img]:w-full [&>img]:object-cover [&>svg]:h-6 [&>svg]:w-6",
className
)}
{...props}
/>
))
AppLauncherRecent.displayName = "AppLauncherRecent"
export {
AppLauncher,
AppLauncherHandle,
AppLauncherHeader,
AppLauncherBack,
AppLauncherSearch,
AppLauncherSearchInput,
AppLauncherBody,
AppLauncherSection,
AppLauncherSectionHeader,
AppLauncherSectionTitle,
AppLauncherViewMore,
AppLauncherList,
AppLauncherItem,
AppLauncherItemIcon,
AppLauncherRecents,
AppLauncherRecent,
}<script setup lang="ts">
import type { HTMLAttributes } from "vue"
import { cn } from "@/lib/utils"
const props = defineProps<{ class?: HTMLAttributes["class"] }>()
</script>
<template>
<!-- The Apps popout: search, recents, and the apps installed in a server. -->
<div
role="dialog"
:class="
cn(
'flex h-[min(680px,calc(100dvh-2rem))] w-full max-w-[504px] flex-col overflow-hidden rounded-md border border-newt-border bg-newt-bg-surface text-newt-text-primary shadow-elevation-high',
props.class
)
"
>
<slot />
</div>
</template><script setup lang="ts">
import type { HTMLAttributes } from "vue"
import { cn } from "@/lib/utils"
const props = defineProps<{ class?: HTMLAttributes["class"] }>()
</script>
<template>
<!-- Grip shown when the launcher is presented as a bottom sheet. -->
<div
aria-hidden="true"
:class="
cn(
'mx-auto mt-2 h-1 w-9 shrink-0 rounded-full bg-newt-border',
props.class
)
"
/>
</template><script setup lang="ts">
import type { HTMLAttributes } from "vue"
import { cn } from "@/lib/utils"
const props = defineProps<{ class?: HTMLAttributes["class"] }>()
</script>
<template>
<div
:class="
cn(
'flex h-14 shrink-0 items-center gap-2 px-2 text-base font-semibold',
props.class
)
"
>
<slot />
</div>
</template><script setup lang="ts">
import type { HTMLAttributes } from "vue"
import { cn } from "@/lib/utils"
const props = defineProps<{ class?: HTMLAttributes["class"] }>()
</script>
<template>
<button
type="button"
:class="
cn(
'inline-flex h-8 w-8 shrink-0 cursor-pointer items-center justify-center rounded-full border-0 bg-newt-bg-elevated p-0 text-newt-text-secondary transition-colors duration-fast ease-newt',
'hover:bg-newt-bg-hover hover:text-newt-text-primary',
'focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-newt-text-link',
props.class
)
"
>
<slot />
</button>
</template><script setup lang="ts">
import type { HTMLAttributes } from "vue"
import { cn } from "@/lib/utils"
const props = defineProps<{ class?: HTMLAttributes["class"] }>()
</script>
<template>
<!-- Search field wrapper; place an `<AppLauncherSearchInput>` inside it. -->
<div :class="cn('shrink-0 px-4 pb-3 pt-4', props.class)">
<div class="relative flex items-center">
<svg
viewBox="0 0 20 20"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
aria-hidden="true"
class="pointer-events-none absolute start-3.5 top-1/2 h-5 w-5 -translate-y-1/2 text-newt-text-muted"
>
<circle cx="9" cy="9" r="5.5" />
<path d="M13 13l4 4" />
</svg>
<slot />
</div>
</div>
</template><script setup lang="ts">
import type { HTMLAttributes } from "vue"
import { cn } from "@/lib/utils"
const props = defineProps<{ class?: HTMLAttributes["class"] }>()
</script>
<template>
<input
type="search"
autocomplete="off"
spellcheck="false"
:class="
cn(
'h-11 w-full appearance-none rounded-md border-2 border-transparent bg-newt-bg-input py-0 ps-11 pe-3 text-base text-newt-text-primary outline-none',
'placeholder:text-newt-text-muted focus:border-newt-brand',
props.class
)
"
/>
</template><script setup lang="ts">
import type { HTMLAttributes } from "vue"
import { cn } from "@/lib/utils"
const props = defineProps<{ class?: HTMLAttributes["class"] }>()
</script>
<template>
<div
:class="
cn(
'flex min-h-0 flex-1 flex-col gap-6 overflow-y-auto px-4 pb-4',
props.class
)
"
>
<slot />
</div>
</template><script setup lang="ts">
import type { HTMLAttributes } from "vue"
import { cn } from "@/lib/utils"
const props = defineProps<{ class?: HTMLAttributes["class"] }>()
</script>
<template>
<section :class="cn('flex flex-col gap-2', props.class)">
<slot />
</section>
</template><script setup lang="ts">
import type { HTMLAttributes } from "vue"
import { cn } from "@/lib/utils"
const props = defineProps<{ class?: HTMLAttributes["class"] }>()
</script>
<template>
<div
:class="cn('flex items-center justify-between gap-2 px-0.5', props.class)"
>
<slot />
</div>
</template><script setup lang="ts">
import type { HTMLAttributes } from "vue"
import { cn } from "@/lib/utils"
const props = defineProps<{ class?: HTMLAttributes["class"] }>()
</script>
<template>
<h2
:class="
cn(
'm-0 inline-flex items-center gap-1 text-base font-medium text-newt-text-primary',
props.class
)
"
>
<slot />
</h2>
</template><script setup lang="ts">
import type { HTMLAttributes } from "vue"
import { cn } from "@/lib/utils"
const props = defineProps<{ class?: HTMLAttributes["class"] }>()
</script>
<template>
<button
type="button"
:class="
cn(
'cursor-pointer rounded-sm border-0 bg-transparent p-0 text-base font-semibold text-newt-text-link no-underline hover:underline',
'focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-newt-text-link',
props.class
)
"
>
<slot />
</button>
</template><script setup lang="ts">
import type { HTMLAttributes } from "vue"
import { cn } from "@/lib/utils"
const props = defineProps<{ class?: HTMLAttributes["class"] }>()
</script>
<template>
<!-- Card that stacks `<AppLauncherItem>` rows behind one rounded surface. -->
<div
:class="
cn(
'flex flex-col overflow-hidden rounded-md bg-newt-bg-elevated',
'[&>*+*]:border-t [&>*+*]:border-newt-border',
props.class
)
"
>
<slot />
</div>
</template><script setup lang="ts">
import type { HTMLAttributes } from "vue"
import { cn } from "@/lib/utils"
const props = withDefaults(
defineProps<{
class?: HTMLAttributes["class"]
name: string
description?: string
/** Marks a paid placement, as the client does for promoted apps. */
promoted?: boolean
}>(),
{ promoted: false }
)
</script>
<template>
<button
type="button"
:class="
cn(
'flex w-full cursor-pointer items-center gap-3 border-0 bg-transparent px-3 py-3 text-start transition-colors duration-fast ease-newt',
'hover:bg-newt-bg-hover',
'focus-visible:outline focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-newt-text-link',
props.class
)
"
>
<slot name="icon" />
<span class="flex min-w-0 flex-1 flex-col gap-0.5">
<span class="flex min-w-0 items-center gap-1.5">
<span
class="truncate text-base font-medium leading-tight text-newt-text-primary"
>
{{ props.name }}
</span>
<span
v-if="props.promoted"
class="shrink-0 rounded-full bg-newt-text-primary px-1.5 py-0.5 text-[10px] font-bold uppercase leading-none tracking-wide text-newt-bg-surface"
>
Promoted
</span>
</span>
<span
v-if="props.description"
class="truncate text-sm leading-snug text-newt-text-muted"
>
{{ props.description }}
</span>
</span>
</button>
</template><script setup lang="ts">
import type { HTMLAttributes } from "vue"
import { cn } from "@/lib/utils"
const props = withDefaults(
defineProps<{
class?: HTMLAttributes["class"]
/** Bots use circular avatars; games and activities keep the squircle. */
round?: boolean
}>(),
{ round: false }
)
</script>
<template>
<span
aria-hidden="true"
:class="
cn(
'inline-flex h-10 w-10 shrink-0 items-center justify-center overflow-hidden bg-newt-bg-base text-newt-text-secondary',
props.round ? 'rounded-full' : 'rounded-[10px]',
'[&>img]:h-full [&>img]:w-full [&>img]:object-cover [&>svg]:h-5 [&>svg]:w-5',
props.class
)
"
>
<slot />
</span>
</template><script setup lang="ts">
import type { HTMLAttributes } from "vue"
import { cn } from "@/lib/utils"
const props = defineProps<{ class?: HTMLAttributes["class"] }>()
</script>
<template>
<!-- Horizontal strip of recently used apps. -->
<div
:class="
cn(
'flex gap-3 overflow-x-auto [scrollbar-width:none] [&::-webkit-scrollbar]:hidden',
props.class
)
"
>
<slot />
</div>
</template><script setup lang="ts">
import type { HTMLAttributes } from "vue"
import { cn } from "@/lib/utils"
const props = defineProps<{ class?: HTMLAttributes["class"] }>()
</script>
<template>
<button
type="button"
:class="
cn(
'inline-flex h-12 w-12 shrink-0 cursor-pointer items-center justify-center overflow-hidden rounded-[14px] border-0 bg-newt-bg-base p-0 text-newt-text-secondary transition-[filter] duration-fast ease-newt',
'hover:brightness-110',
'focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-newt-text-link',
'[&>img]:h-full [&>img]:w-full [&>img]:object-cover [&>svg]:h-6 [&>svg]:w-6',
props.class
)
"
>
<slot />
</button>
</template>export { default as AppLauncher } from "./AppLauncher.vue"
export { default as AppLauncherHandle } from "./AppLauncherHandle.vue"
export { default as AppLauncherHeader } from "./AppLauncherHeader.vue"
export { default as AppLauncherBack } from "./AppLauncherBack.vue"
export { default as AppLauncherSearch } from "./AppLauncherSearch.vue"
export { default as AppLauncherSearchInput } from "./AppLauncherSearchInput.vue"
export { default as AppLauncherBody } from "./AppLauncherBody.vue"
export { default as AppLauncherSection } from "./AppLauncherSection.vue"
export { default as AppLauncherSectionHeader } from "./AppLauncherSectionHeader.vue"
export { default as AppLauncherSectionTitle } from "./AppLauncherSectionTitle.vue"
export { default as AppLauncherViewMore } from "./AppLauncherViewMore.vue"
export { default as AppLauncherList } from "./AppLauncherList.vue"
export { default as AppLauncherItem } from "./AppLauncherItem.vue"
export { default as AppLauncherItemIcon } from "./AppLauncherItemIcon.vue"
export { default as AppLauncherRecents } from "./AppLauncherRecents.vue"
export { default as AppLauncherRecent } from "./AppLauncherRecent.vue"Update the import paths
The files import @/lib/utils and sibling components through the aliases in your components.json. Point them at wherever those live in your project.
Usage
import { AppLauncher } from "@/components/ui/app-launcher"
export function Example() {
return <AppLauncher />
}<script setup lang="ts">
import { AppLauncher, AppLauncherHandle, AppLauncherHeader, AppLauncherBack, AppLauncherSearch, AppLauncherSearchInput, AppLauncherBody, AppLauncherSection, AppLauncherSectionHeader, AppLauncherSectionTitle, AppLauncherViewMore, AppLauncherList, AppLauncherItem, AppLauncherItemIcon, AppLauncherRecents, AppLauncherRecent } from "@/components/ui/app-launcher"
</script>
<template>
<AppLauncher />
</template>API reference
AppLauncherItem
name?string
Required name value.
stringdescription?string | undefined
Optional description value.
string | undefinedicon?React.ReactNode | undefined
Icon for the app; pair with `<AppLauncherItemIcon>`.
React.ReactNode | undefinedpromoted?boolean | undefined
Marks a paid placement, as the client does for promoted apps.
boolean | undefinedAppLauncherItemIcon
round?boolean | undefined
Bots use circular avatars; games and activities keep the squircle.
boolean | undefinedThe registry exports AppLauncher, AppLauncherHandle, AppLauncherHeader, AppLauncherBack, AppLauncherSearch, AppLauncherSearchInput, AppLauncherBody, AppLauncherSection, AppLauncherSectionHeader, AppLauncherSectionTitle, AppLauncherViewMore, AppLauncherList, AppLauncherItem, AppLauncherItemIcon, AppLauncherRecents, AppLauncherRecent. Parts without a table accept the native attributes for the element they render; React uses className, while Vue uses class and fallthrough attributes.
Accessibility
Give the launcher an accessible heading, keep search labelled, and expose the active item while keyboard focus moves through the results.
Tokens
App Launcher reads its colours, radii and motion from the --newt-* tokens installed with the library. Override a token and every component that reads it follows, including this one — see the token layer.