diff --git a/.changeset/tidy-registry-titles.md b/.changeset/tidy-registry-titles.md
new file mode 100644
index 00000000000..df800880ba8
--- /dev/null
+++ b/.changeset/tidy-registry-titles.md
@@ -0,0 +1,5 @@
+---
+"shadcn": patch
+---
+
+Include registry item titles in `searchRegistries` results and fuzzy matching.
diff --git a/apps/v4/content/docs/registry/api-reference.mdx b/apps/v4/content/docs/registry/api-reference.mdx
index d4642ea7423..a88d98f488f 100644
--- a/apps/v4/content/docs/registry/api-reference.mdx
+++ b/apps/v4/content/docs/registry/api-reference.mdx
@@ -243,6 +243,7 @@ Returns matching items wrapped in pagination metadata:
"items": [
{
"name": "button",
+ "title": "Button",
"type": "registry:ui",
"description": "A button component.",
"registry": "@shadcn",
diff --git a/apps/v4/registry/directory.json b/apps/v4/registry/directory.json
index 6f70f71428b..8509b989a32 100644
--- a/apps/v4/registry/directory.json
+++ b/apps/v4/registry/directory.json
@@ -6,6 +6,13 @@
"description": "Puffy, pastel claymorphism components and app blocks for React, built with Tailwind CSS v4 and Radix UI.",
"logo": ""
},
+ {
+ "name": "@23rd",
+ "homepage": "https://23rd.dev",
+ "url": "https://23rd.dev/r/{name}.json",
+ "description": "Opinionated components for shippers.",
+ "logo": ""
+ },
{
"name": "@7ovr",
"homepage": "https://7ovr.com",
diff --git a/packages/shadcn/src/registry/schema.ts b/packages/shadcn/src/registry/schema.ts
index c9b144e277b..f19a78ad639 100644
--- a/packages/shadcn/src/registry/schema.ts
+++ b/packages/shadcn/src/registry/schema.ts
@@ -293,6 +293,7 @@ export const registryResolvedItemsTreeSchema = registryItemCommonSchema
export const searchResultItemSchema = z.object({
name: z.string(),
+ title: z.string().optional(),
type: z.string().optional(),
description: z.string().optional(),
registry: z.string(),
diff --git a/packages/shadcn/src/registry/search.test.ts b/packages/shadcn/src/registry/search.test.ts
index b4a0aff7d57..bae7ea00ea4 100644
--- a/packages/shadcn/src/registry/search.test.ts
+++ b/packages/shadcn/src/registry/search.test.ts
@@ -31,6 +31,7 @@ describe("searchRegistries", () => {
items: [
{
name: "button",
+ title: "Button",
type: "registry:ui",
description: "A button component",
},
@@ -64,6 +65,7 @@ describe("searchRegistries", () => {
items: [
{
name: "button",
+ title: "Button",
type: "registry:ui",
description: "A button component",
registry: "@shadcn",
@@ -95,6 +97,47 @@ describe("searchRegistries", () => {
mockGetRegistry.mockRestore()
})
+ it("includes titles in the SDK output and searches them", async () => {
+ vi.mock("./api", () => ({
+ getRegistry: vi.fn(),
+ }))
+
+ const mockGetRegistry = vi.mocked(getRegistry)
+
+ mockGetRegistry.mockResolvedValue({
+ name: "test/registry",
+ homepage: "https://test.com",
+ items: [
+ {
+ name: "account-menu",
+ title: "User Settings",
+ type: "registry:ui",
+ description: "An account menu",
+ },
+ {
+ name: "button",
+ type: "registry:ui",
+ description: "A button component",
+ },
+ ],
+ })
+
+ const results = await searchRegistries(["@test"], { query: "settings" })
+
+ expect(results.items).toEqual([
+ {
+ name: "account-menu",
+ title: "User Settings",
+ type: "registry:ui",
+ description: "An account menu",
+ registry: "@test",
+ addCommandArgument: "@test/account-menu",
+ },
+ ])
+
+ mockGetRegistry.mockRestore()
+ })
+
it("should apply search filter when query is provided", async () => {
vi.mock("./api", () => ({
getRegistry: vi.fn(),
diff --git a/packages/shadcn/src/registry/search.ts b/packages/shadcn/src/registry/search.ts
index 11f8dc07352..4b03977fe55 100644
--- a/packages/shadcn/src/registry/search.ts
+++ b/packages/shadcn/src/registry/search.ts
@@ -160,6 +160,7 @@ async function searchRegistriesWithContext(
const itemsWithRegistry = (outcome.value.items || []).map((item) => ({
name: item.name,
+ title: item.title,
type: item.type,
description: item.description,
registry,
@@ -207,7 +208,7 @@ async function searchRegistriesWithContext(
localItems = searchItems(localItems, {
query,
limit: localItems.length,
- keys: ["name", "description"],
+ keys: ["name", "title", "description"],
}) as z.infer[]
}
@@ -259,6 +260,7 @@ async function searchRegistriesWithContext(
const searchableItemSchema = z
.object({
name: z.string(),
+ title: z.string().optional(),
type: z.string().optional(),
description: z.string().optional(),
registry: z.string().optional(),
@@ -271,6 +273,7 @@ type SearchableItem = z.infer
function searchItems<
T extends {
name: string
+ title?: string
type?: string
description?: string
addCommandArgument?: string