Skip to content
edmi-ui

Model Selector

Searchable model picker in a command dialog, with provider logos and grouped lists.

StackBlitz ↗StackBlitz ↗StackBlitz ↗

Built on command and dialog.

Terminal window
npx shadcn@latest add @edmi-ui/ai-model-selector
Terminal window
pnpm dlx shadcn@latest add @edmi-ui/ai-model-selector
Terminal window
yarn dlx shadcn@latest add @edmi-ui/ai-model-selector
Terminal window
bunx shadcn@latest add @edmi-ui/ai-model-selector

Pick your package manager above; the choice is remembered. Add the @edmi-ui registry first, see Getting started.

Terminal window
npx shadcn-vue@latest add @edmi-ui/ai-model-selector
Terminal window
pnpm dlx shadcn-vue@latest add @edmi-ui/ai-model-selector
Terminal window
yarn dlx shadcn-vue@latest add @edmi-ui/ai-model-selector
Terminal window
bunx shadcn-vue@latest add @edmi-ui/ai-model-selector

Pick your package manager above; the choice is remembered. Add the @edmi-ui registry first, see Getting started.

Terminal window
npx shadcn-svelte@latest add https://viandwi24.github.io/edmi-ui/r/svelte/ai-model-selector.json
Terminal window
pnpm dlx shadcn-svelte@latest add https://viandwi24.github.io/edmi-ui/r/svelte/ai-model-selector.json
Terminal window
yarn dlx shadcn-svelte@latest add https://viandwi24.github.io/edmi-ui/r/svelte/ai-model-selector.json
Terminal window
bunx --bun shadcn-svelte@latest add https://viandwi24.github.io/edmi-ui/r/svelte/ai-model-selector.json

Pick your package manager above; the choice is remembered. Add the @edmi-ui registry first, see Getting started.

import {
ModelSelector,
ModelSelectorContent,
ModelSelectorEmpty,
ModelSelectorInput,
ModelSelectorList,
ModelSelectorName,
ModelSelectorTrigger,
} from "@/components/ai/model-selector";
import { Button } from "@/components/ui/button";
<ModelSelector>
<ModelSelectorTrigger render={<Button variant="outline" />}>
<ModelSelectorName></ModelSelectorName>
</ModelSelectorTrigger>
<ModelSelectorContent>
<ModelSelectorInput placeholder="Search models..." />
<ModelSelectorList>
<ModelSelectorEmpty>No models found.</ModelSelectorEmpty>
</ModelSelectorList>
</ModelSelectorContent>
</ModelSelector>
<script setup lang="ts">
import {
ModelSelector,
ModelSelectorContent,
ModelSelectorEmpty,
ModelSelectorGroup,
ModelSelectorInput,
ModelSelectorItem,
ModelSelectorList,
ModelSelectorLogo,
ModelSelectorName,
ModelSelectorShortcut,
ModelSelectorTrigger,
} from "@/components/ai/model-selector";
import { Button } from "@/components/ui/button";
</script>
<template>
<ModelSelector>
<ModelSelectorTrigger as-child>
<Button variant="outline">
<ModelSelectorLogo />
<ModelSelectorName></ModelSelectorName>
</Button>
</ModelSelectorTrigger>
<ModelSelectorContent>
<ModelSelectorInput placeholder="Search models..." />
<ModelSelectorList>
<ModelSelectorEmpty>No models found.</ModelSelectorEmpty>
<ModelSelectorGroup :key="group.provider" :heading="group.label">
<ModelSelectorItem
v-for="item in group.items"
:key="item"
:value="item"
@select="pick(item)"
>
<ModelSelectorLogo :provider="group.provider" />
<ModelSelectorName>{{ item }}</ModelSelectorName>
<ModelSelectorShortcut></ModelSelectorShortcut>
</ModelSelectorItem>
</ModelSelectorGroup>
</ModelSelectorList>
</ModelSelectorContent>
</ModelSelector>
</template>
<script lang="ts">
import {
ModelSelector,
ModelSelectorLogo,
ModelSelectorName,
ModelSelectorTrigger,
} from "$lib/components/ai/model-selector";
import { Button } from "$lib/components/ui/button";
</script>
<ModelSelector bind:open>
<ModelSelectorTrigger>
{#snippet child({ props })}
<Button variant="outline" {...props}>
{#if current}<ModelSelectorLogo />{/if}
<ModelSelectorName></ModelSelectorName>
</Button>
{/snippet}
</ModelSelectorTrigger>
<!-- … -->
</ModelSelector>
registry/components/ai/model-selector.tsx
"use client";
import { cn } from "cn";
import type { ComponentProps, ReactNode } from "react";
import {
Command,
CommandDialog,
CommandEmpty,
CommandGroup,
CommandInput,
CommandItem,
CommandList,
CommandSeparator,
CommandShortcut,
} from "@/registry/edmi/ui/command";
import {
Dialog,
DialogContent,
DialogTitle,
DialogTrigger,
} from "@/registry/edmi/ui/dialog";
// Built on ui/command + ui/dialog (DESIGN §5b): the palette body is the Edmi Command, the popup the Edmi Dialog.
export type ModelSelectorProps = ComponentProps<typeof Dialog>;
export const ModelSelector = (props: ModelSelectorProps) => (
<Dialog {...props} />
);
export type ModelSelectorTriggerProps = ComponentProps<typeof DialogTrigger>;
/** Pass the visible trigger with `render`, e.g. `render={<Button variant="outline" />}`. */
export const ModelSelectorTrigger = (props: ModelSelectorTriggerProps) => (
<DialogTrigger data-slot="ai-model-selector-trigger" {...props} />
);
export type ModelSelectorContentProps = ComponentProps<typeof DialogContent> & {
title?: ReactNode;
};
export const ModelSelectorContent = ({
className,
children,
title = "Model Selector",
...props
}: ModelSelectorContentProps) => (
<DialogContent
data-slot="ai-model-selector-content"
aria-describedby={undefined}
showCloseButton={false}
className={cn(
"top-1/3 translate-y-0 gap-0 overflow-hidden p-0 sm:max-w-md",
className,
)}
{...props}
>
<DialogTitle className="sr-only">{title}</DialogTitle>
<Command className="rounded-none">{children}</Command>
</DialogContent>
);
export type ModelSelectorDialogProps = ComponentProps<typeof CommandDialog>;
export const ModelSelectorDialog = (props: ModelSelectorDialogProps) => (
<CommandDialog {...props} />
);
export type ModelSelectorInputProps = ComponentProps<typeof CommandInput>;
export const ModelSelectorInput = ({
className,
...props
}: ModelSelectorInputProps) => (
<CommandInput className={cn("h-auto py-3.5", className)} {...props} />
);
export type ModelSelectorListProps = ComponentProps<typeof CommandList>;
export const ModelSelectorList = (props: ModelSelectorListProps) => (
<CommandList {...props} />
);
export type ModelSelectorEmptyProps = ComponentProps<typeof CommandEmpty>;
export const ModelSelectorEmpty = (props: ModelSelectorEmptyProps) => (
<CommandEmpty {...props} />
);
export type ModelSelectorGroupProps = ComponentProps<typeof CommandGroup>;
export const ModelSelectorGroup = (props: ModelSelectorGroupProps) => (
<CommandGroup {...props} />
);
export type ModelSelectorItemProps = ComponentProps<typeof CommandItem>;
export const ModelSelectorItem = (props: ModelSelectorItemProps) => (
<CommandItem {...props} />
);
export type ModelSelectorShortcutProps = ComponentProps<typeof CommandShortcut>;
export const ModelSelectorShortcut = (props: ModelSelectorShortcutProps) => (
<CommandShortcut {...props} />
);
export type ModelSelectorSeparatorProps = ComponentProps<
typeof CommandSeparator
>;
export const ModelSelectorSeparator = (props: ModelSelectorSeparatorProps) => (
<CommandSeparator {...props} />
);
export type ModelSelectorLogoProps = Omit<
ComponentProps<"img">,
"src" | "alt"
> & {
/** A models.dev provider id (`anthropic`, `openai`, `google`, …); any string works. */
provider:
| "anthropic"
| "openai"
| "google"
| "xai"
| "mistral"
| "groq"
| "deepseek"
| "perplexity"
| "meta"
| (string & {});
};
export const ModelSelectorLogo = ({
provider,
className,
...props
}: ModelSelectorLogoProps) => (
<img
{...props}
alt={`${provider} logo`}
className={cn("size-3.5 dark:invert", className)}
height={14}
src={`https://models.dev/logos/${provider}.svg`}
width={14}
/>
);
export type ModelSelectorLogoGroupProps = ComponentProps<"div">;
/** Overlapping logos, each on a solid --card disc with a 1px --border ring. */
export const ModelSelectorLogoGroup = ({
className,
...props
}: ModelSelectorLogoGroupProps) => (
<div
className={cn(
"flex shrink-0 items-center -space-x-1 [&>img]:rounded-full [&>img]:border [&>img]:border-border [&>img]:bg-card [&>img]:p-px",
className,
)}
{...props}
/>
);
export type ModelSelectorNameProps = ComponentProps<"span">;
export const ModelSelectorName = ({
className,
...props
}: ModelSelectorNameProps) => (
<span className={cn("flex-1 truncate text-left", className)} {...props} />
);
registry/components/ai/model-selector/ModelSelector.vue
<script setup lang="ts">
import type { DialogRootEmits, DialogRootProps } from "reka-ui"
import { useForwardPropsEmits } from "reka-ui"
import { Dialog } from "@/registry/edmi/ui/dialog"
// Built on ui/command + ui/dialog (DESIGN §5b): the palette body is the Edmi Command, the popup the Edmi Dialog.
const props = defineProps<DialogRootProps>()
const emits = defineEmits<DialogRootEmits>()
const forwarded = useForwardPropsEmits(props, emits)
</script>
<template>
<Dialog v-bind="forwarded">
<slot />
</Dialog>
</template>
registry/components/ai/model-selector/ModelSelectorContent.vue
<script setup lang="ts">
import type { AcceptableValue } from "reka-ui"
import type { HTMLAttributes } from "vue"
import { cn } from "@/registry/edmi/lib/utils"
import { Command } from "@/registry/edmi/ui/command"
import { DialogContent, DialogDescription, DialogTitle } from "@/registry/edmi/ui/dialog"
defineOptions({ inheritAttrs: false })
const props = withDefaults(defineProps<{
title?: string
class?: HTMLAttributes["class"]
}>(), {
title: "Model Selector",
})
const value = defineModel<AcceptableValue | AcceptableValue[]>()
</script>
<template>
<DialogContent
v-bind="$attrs"
data-slot="ai-model-selector-content"
:show-close-button="false"
:class="cn('top-1/3 translate-y-0 gap-0 overflow-hidden p-0 sm:max-w-md', props.class)"
>
<DialogTitle class="sr-only">
{{ props.title }}
</DialogTitle>
<DialogDescription class="sr-only">
Search and pick a model
</DialogDescription>
<Command v-model="value" class="rounded-none **:data-[slot=command-input-wrapper]:h-auto">
<slot />
</Command>
</DialogContent>
</template>
registry/components/ai/model-selector/ModelSelectorDialog.vue
<script setup lang="ts">
import type { DialogRootEmits, DialogRootProps } from "reka-ui"
import { useForwardPropsEmits } from "reka-ui"
import { CommandDialog } from "@/registry/edmi/ui/command"
const props = defineProps<DialogRootProps & { title?: string, description?: string }>()
const emits = defineEmits<DialogRootEmits>()
const forwarded = useForwardPropsEmits(props, emits)
</script>
<template>
<CommandDialog v-bind="forwarded">
<slot />
</CommandDialog>
</template>
registry/components/ai/model-selector/ModelSelectorEmpty.vue
<script setup lang="ts">
import { CommandEmpty } from "@/registry/edmi/ui/command"
type Props = InstanceType<typeof CommandEmpty>["$props"]
interface ModelSelectorProps extends /* @vue-ignore */ Props {}
defineProps<ModelSelectorProps>()
</script>
<template>
<CommandEmpty>
<slot />
</CommandEmpty>
</template>
registry/components/ai/model-selector/ModelSelectorGroup.vue
<script setup lang="ts">
import { CommandGroup } from "@/registry/edmi/ui/command"
type Props = InstanceType<typeof CommandGroup>["$props"]
interface ModelSelectorProps extends /* @vue-ignore */ Props {}
defineProps<ModelSelectorProps>()
</script>
<template>
<CommandGroup>
<slot />
</CommandGroup>
</template>
registry/components/ai/model-selector/ModelSelectorInput.vue
<script setup lang="ts">
import type { HTMLAttributes } from "vue"
import { cn } from "@/registry/edmi/lib/utils"
import { CommandInput } from "@/registry/edmi/ui/command"
const props = defineProps<{
class?: HTMLAttributes["class"]
placeholder?: string
}>()
</script>
<template>
<CommandInput
:placeholder="props.placeholder"
:class="cn('py-3.5', props.class)"
/>
</template>
registry/components/ai/model-selector/ModelSelectorItem.vue
<script setup lang="ts">
import { CommandItem } from "@/registry/edmi/ui/command"
type Props = InstanceType<typeof CommandItem>["$props"]
interface ModelSelectorProps extends /* @vue-ignore */ Props {}
defineOptions({ inheritAttrs: false })
defineProps<ModelSelectorProps>()
</script>
<template>
<CommandItem v-bind="($attrs as any)">
<slot />
</CommandItem>
</template>
registry/components/ai/model-selector/ModelSelectorList.vue
<script setup lang="ts">
import { CommandList } from "@/registry/edmi/ui/command"
type Props = InstanceType<typeof CommandList>["$props"]
interface ModelSelectorProps extends /* @vue-ignore */ Props {}
defineProps<ModelSelectorProps>()
</script>
<template>
<CommandList>
<slot />
</CommandList>
</template>
registry/components/ai/model-selector/ModelSelectorLogo.vue
<script setup lang="ts">
import type { HTMLAttributes } from "vue"
import { cn } from "@/registry/edmi/lib/utils"
const props = defineProps<{
/** A models.dev provider id (`anthropic`, `openai`, `google`, ...); any string works. */
provider: string
class?: HTMLAttributes["class"]
}>()
</script>
<template>
<img
:alt="`${props.provider} logo`"
:class="cn('size-3.5 dark:invert', props.class)"
height="14"
:src="`https://models.dev/logos/${props.provider}.svg`"
width="14"
>
</template>
registry/components/ai/model-selector/ModelSelectorLogoGroup.vue
<script setup lang="ts">
import type { HTMLAttributes } from "vue"
import { cn } from "@/registry/edmi/lib/utils"
const props = defineProps<{
class?: HTMLAttributes["class"]
}>()
</script>
<template>
<!-- Overlapping logos, each on a solid --card disc with a 1px --border ring. -->
<div
:class="cn(
'flex shrink-0 items-center -space-x-1 [&>img]:rounded-full [&>img]:border [&>img]:border-border [&>img]:bg-card [&>img]:p-px',
props.class,
)"
>
<slot />
</div>
</template>
registry/components/ai/model-selector/ModelSelectorName.vue
<script setup lang="ts">
import type { HTMLAttributes } from "vue"
import { cn } from "@/registry/edmi/lib/utils"
const props = defineProps<{
class?: HTMLAttributes["class"]
}>()
</script>
<template>
<span :class="cn('flex-1 truncate text-left', props.class)">
<slot />
</span>
</template>
registry/components/ai/model-selector/ModelSelectorSeparator.vue
<script setup lang="ts">
import { CommandSeparator } from "@/registry/edmi/ui/command"
type Props = InstanceType<typeof CommandSeparator>["$props"]
interface ModelSelectorProps extends /* @vue-ignore */ Props {}
defineProps<ModelSelectorProps>()
</script>
<template>
<CommandSeparator>
<slot />
</CommandSeparator>
</template>
registry/components/ai/model-selector/ModelSelectorShortcut.vue
<script setup lang="ts">
import { CommandShortcut } from "@/registry/edmi/ui/command"
type Props = InstanceType<typeof CommandShortcut>["$props"]
interface ModelSelectorProps extends /* @vue-ignore */ Props {}
defineProps<ModelSelectorProps>()
</script>
<template>
<CommandShortcut>
<slot />
</CommandShortcut>
</template>
registry/components/ai/model-selector/ModelSelectorTrigger.vue
<script setup lang="ts">
import type { DialogTriggerProps } from "reka-ui"
import { DialogTrigger } from "@/registry/edmi/ui/dialog"
// Pass the visible trigger with `as-child`, e.g. <ModelSelectorTrigger as-child><Button variant="outline" /></ModelSelectorTrigger>.
const props = defineProps<DialogTriggerProps>()
</script>
<template>
<DialogTrigger data-slot="ai-model-selector-trigger" v-bind="props">
<slot />
</DialogTrigger>
</template>
registry/components/ai/model-selector/index.ts
export { default as ModelSelector } from "./ModelSelector.vue";
export { default as ModelSelectorContent } from "./ModelSelectorContent.vue";
export { default as ModelSelectorDialog } from "./ModelSelectorDialog.vue";
export { default as ModelSelectorEmpty } from "./ModelSelectorEmpty.vue";
export { default as ModelSelectorGroup } from "./ModelSelectorGroup.vue";
export { default as ModelSelectorInput } from "./ModelSelectorInput.vue";
export { default as ModelSelectorItem } from "./ModelSelectorItem.vue";
export { default as ModelSelectorList } from "./ModelSelectorList.vue";
export { default as ModelSelectorLogo } from "./ModelSelectorLogo.vue";
export { default as ModelSelectorLogoGroup } from "./ModelSelectorLogoGroup.vue";
export { default as ModelSelectorName } from "./ModelSelectorName.vue";
export { default as ModelSelectorSeparator } from "./ModelSelectorSeparator.vue";
export { default as ModelSelectorShortcut } from "./ModelSelectorShortcut.vue";
export { default as ModelSelectorTrigger } from "./ModelSelectorTrigger.vue";
src/lib/registry/ai/model-selector/index.ts
import Root from "./model-selector.svelte";
import Content from "./model-selector-content.svelte";
import Dialog from "./model-selector-dialog.svelte";
import Empty from "./model-selector-empty.svelte";
import Group from "./model-selector-group.svelte";
import Input from "./model-selector-input.svelte";
import Item from "./model-selector-item.svelte";
import List from "./model-selector-list.svelte";
import Logo from "./model-selector-logo.svelte";
import LogoGroup from "./model-selector-logo-group.svelte";
import Name from "./model-selector-name.svelte";
import Separator from "./model-selector-separator.svelte";
import Shortcut from "./model-selector-shortcut.svelte";
import Trigger from "./model-selector-trigger.svelte";
export {
Content,
Content as ModelSelectorContent,
Dialog,
Dialog as ModelSelectorDialog,
Empty,
Empty as ModelSelectorEmpty,
Group,
Group as ModelSelectorGroup,
Input,
Input as ModelSelectorInput,
Item,
Item as ModelSelectorItem,
List,
List as ModelSelectorList,
Logo,
Logo as ModelSelectorLogo,
LogoGroup,
LogoGroup as ModelSelectorLogoGroup,
Name,
Name as ModelSelectorName,
Root,
//
Root as ModelSelector,
Separator,
Separator as ModelSelectorSeparator,
Shortcut,
Shortcut as ModelSelectorShortcut,
Trigger,
Trigger as ModelSelectorTrigger,
};
src/lib/registry/ai/model-selector/model-selector-content.svelte
<script lang="ts">
import { Command } from "$lib/registry/ui/command/index.js";
import {
DialogContent,
DialogDescription,
DialogTitle,
} from "$lib/registry/ui/dialog/index.js";
import { cn } from "$lib/utils.js";
import type { ComponentProps, Snippet } from "svelte";
let {
class: className,
children,
title = "Model Selector",
value = $bindable(""),
...restProps
}: Omit<ComponentProps<typeof DialogContent>, "children" | "showCloseButton"> & {
title?: string;
value?: string;
children?: Snippet;
} = $props();
</script>
<DialogContent
data-slot="ai-model-selector-content"
showCloseButton={false}
class={cn("top-1/3 translate-y-0 gap-0 overflow-hidden p-0 sm:max-w-md", className)}
{...restProps}
>
<DialogTitle class="sr-only">{title}</DialogTitle>
<DialogDescription class="sr-only">Search and pick a model</DialogDescription>
<Command bind:value class="rounded-none **:data-[slot=command-input-wrapper]:h-auto">
{@render children?.()}
</Command>
</DialogContent>
src/lib/registry/ai/model-selector/model-selector-dialog.svelte
<script lang="ts">
import { CommandDialog } from "$lib/registry/ui/command/index.js";
import type { ComponentProps } from "svelte";
let {
open = $bindable(false),
value = $bindable(""),
...restProps
}: ComponentProps<typeof CommandDialog> = $props();
</script>
<CommandDialog bind:open bind:value {...restProps} />
src/lib/registry/ai/model-selector/model-selector-empty.svelte
<script lang="ts">
import { CommandEmpty } from "$lib/registry/ui/command/index.js";
import type { ComponentProps } from "svelte";
let { ref = $bindable(null), children, ...restProps }: ComponentProps<typeof CommandEmpty> = $props();
</script>
<CommandEmpty bind:ref {...restProps}>
{@render children?.()}
</CommandEmpty>
src/lib/registry/ai/model-selector/model-selector-group.svelte
<script lang="ts">
import { CommandGroup } from "$lib/registry/ui/command/index.js";
import type { ComponentProps } from "svelte";
let { ref = $bindable(null), children, ...restProps }: ComponentProps<typeof CommandGroup> = $props();
</script>
<CommandGroup bind:ref {...restProps}>
{@render children?.()}
</CommandGroup>
src/lib/registry/ai/model-selector/model-selector-input.svelte
<script lang="ts">
import { CommandInput } from "$lib/registry/ui/command/index.js";
import { cn } from "$lib/utils.js";
import type { ComponentProps } from "svelte";
let {
ref = $bindable(null),
value = $bindable(""),
class: className,
...restProps
}: ComponentProps<typeof CommandInput> = $props();
</script>
<CommandInput bind:ref bind:value class={cn("py-3.5", className)} {...restProps} />
src/lib/registry/ai/model-selector/model-selector-item.svelte
<script lang="ts">
import { CommandItem } from "$lib/registry/ui/command/index.js";
import type { ComponentProps } from "svelte";
let { ref = $bindable(null), children, ...restProps }: ComponentProps<typeof CommandItem> = $props();
</script>
<CommandItem bind:ref {...restProps}>
{@render children?.()}
</CommandItem>
src/lib/registry/ai/model-selector/model-selector-list.svelte
<script lang="ts">
import { CommandList } from "$lib/registry/ui/command/index.js";
import type { ComponentProps } from "svelte";
let { ref = $bindable(null), children, ...restProps }: ComponentProps<typeof CommandList> = $props();
</script>
<CommandList bind:ref {...restProps}>
{@render children?.()}
</CommandList>
src/lib/registry/ai/model-selector/model-selector-logo-group.svelte
<script lang="ts">
import { cn } from "$lib/utils.js";
import type { Snippet } from "svelte";
import type { HTMLAttributes } from "svelte/elements";
let {
class: className,
children,
...restProps
}: HTMLAttributes<HTMLDivElement> & { children?: Snippet } = $props();
</script>
<!-- Overlapping logos, each on a solid --card disc with a 1px --border ring. -->
<div
class={cn(
"flex shrink-0 items-center -space-x-1 [&>img]:rounded-full [&>img]:border [&>img]:border-border [&>img]:bg-card [&>img]:p-px",
className
)}
{...restProps}
>
{@render children?.()}
</div>
src/lib/registry/ai/model-selector/model-selector-logo.svelte
<script lang="ts">
import { cn } from "$lib/utils.js";
import type { HTMLImgAttributes } from "svelte/elements";
let {
provider,
class: className,
...restProps
}: Omit<HTMLImgAttributes, "src" | "alt"> & {
/** A models.dev provider id (`anthropic`, `openai`, `google`, ...); any string works. */
provider: string;
} = $props();
</script>
<img
{...restProps}
alt="{provider} logo"
class={cn("size-3.5 dark:invert", className)}
height="14"
src="https://models.dev/logos/{provider}.svg"
width="14"
/>
src/lib/registry/ai/model-selector/model-selector-name.svelte
<script lang="ts">
import { cn } from "$lib/utils.js";
import type { Snippet } from "svelte";
import type { HTMLAttributes } from "svelte/elements";
let {
class: className,
children,
...restProps
}: HTMLAttributes<HTMLSpanElement> & { children?: Snippet } = $props();
</script>
<span class={cn("flex-1 truncate text-left", className)} {...restProps}>
{@render children?.()}
</span>
src/lib/registry/ai/model-selector/model-selector-separator.svelte
<script lang="ts">
import { CommandSeparator } from "$lib/registry/ui/command/index.js";
import type { ComponentProps } from "svelte";
let { ref = $bindable(null), children, ...restProps }: ComponentProps<typeof CommandSeparator> = $props();
</script>
<CommandSeparator bind:ref {...restProps}>
{@render children?.()}
</CommandSeparator>
src/lib/registry/ai/model-selector/model-selector-shortcut.svelte
<script lang="ts">
import { CommandShortcut } from "$lib/registry/ui/command/index.js";
import type { ComponentProps } from "svelte";
let { ref = $bindable(null), children, ...restProps }: ComponentProps<typeof CommandShortcut> = $props();
</script>
<CommandShortcut bind:ref {...restProps}>
{@render children?.()}
</CommandShortcut>
src/lib/registry/ai/model-selector/model-selector-trigger.svelte
<script lang="ts">
import { DialogTrigger } from "$lib/registry/ui/dialog/index.js";
import type { ComponentProps } from "svelte";
// Pass the visible trigger with the `child` snippet, e.g. `{#snippet child({ props })}<Button {...props} />{/snippet}`.
let { ref = $bindable(null), ...restProps }: ComponentProps<typeof DialogTrigger> = $props();
</script>
<DialogTrigger bind:ref data-slot="ai-model-selector-trigger" {...restProps} />
src/lib/registry/ai/model-selector/model-selector.svelte
<script lang="ts">
import { Dialog } from "$lib/registry/ui/dialog/index.js";
import type { ComponentProps } from "svelte";
// Built on ui/command + ui/dialog (DESIGN §5b): the palette body is the Edmi Command, the popup the Edmi Dialog.
let { open = $bindable(false), ...restProps }: ComponentProps<typeof Dialog> = $props();
</script>
<Dialog bind:open {...restProps} />
Part Prop Type Default
ModelSelector Dialog props (open, onOpenChange)
ModelSelectorTrigger render element, e.g. <Button variant="outline" />
ModelSelectorContent title ReactNode (screen reader) Model Selector
ModelSelectorLogo provider models.dev provider id
ModelSelectorInput/List/Empty/Group/Item/Shortcut/Separator/Name/LogoGroup, ModelSelectorDialog

Logos load from https://models.dev/logos/<provider>.svg.

  • Trigger: React render={<Button />}, Vue as-child, Svelte {#snippet child({ props })}. Item selection: React onSelect, Vue @select, Svelte onSelect.
  • The popup is the Edmi dialog with the command palette inside (no close button, search first).