Recently, I participated as a partner in the Builders Weekend hackathon, which inspired me to develop a React component that works with the shadcn/ui CLI. Iโd like to share the step-by-step process and some of the highlights!
What I made
Agents Kit helps you build AI Chat apps instantly with Vercel AI SDK.
import * as React from "react"
import { useChat } from '@ai-sdk/react'
import { ChatForm } from '@/components/agents-kit/chat-form';
export function ChatFormDemo() {
const { input, handleInputChange, handleSubmit } = useChat({});
return (
<ChatForm
inputValue={input}
onInputChange={handleInputChange}
onSubmit={handleSubmit}
/>
)
}
TL;DR
How to Develop
- Start a Next.js project. Develop the React component you want to distribute in the
/registry
directory. - Write a
registry.json
once your component is complete to configure its distribution. - Run
shadcn build
to build a JSON file for each component. The files will be output to the public directory. - Deploy your Next.js project to publish the JSON files. It is recommended to develop your Next.js project as the documentation site itself.
Highlights
- You can develop it as a regular React project. You can get started easily since thereโs no need to bundle it as a component library.
- Dependency management is great. Itโs convenient that, upon installing a component, the necessary npm packages and shadcn/ui components are automatically installed.
How to build
1. Initialize a React Project and Develop Your Component
Start a React project using Next.js or Vite. Developing components for the shadcn/ui CLI follows the same straightforward process as with any regular React project.
Below is an example of a component called ChatForm, which is actually distributed as part of Agents Kit.
// registry/agents-kit/chat-form.tsx
"use client";
import React, { FC, KeyboardEvent, PropsWithChildren, useRef } from "react";
import { AutosizeTextarea } from "./autosize-textarea";
import { ChatActionIconButton, ChatActions } from "./chat-actions";
import { Button } from "@/components/ui/button";
import { LucideArrowUp, LucidePlus } from "lucide-react";
import { Card } from "@/components/ui/card";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
export interface ChatFormProps {
inputValue?: string;
onInputChange?: (
e:
| React.ChangeEvent<HTMLInputElement>
| React.ChangeEvent<HTMLTextAreaElement>
) => void;
onFileChange?: (e: React.ChangeEvent<HTMLInputElement>) => void;
onSubmit?: (e: React.FormEvent<HTMLFormElement>) => void;
actions?: React.ReactNode;
}
const ChatForm: FC<PropsWithChildren<ChatFormProps>> = (props) => {
const fileInputRef = useRef<HTMLInputElement>(null);
const submitButtonRef = useRef<HTMLButtonElement>(null);
const handleKeyDown = React.useCallback(
(e: KeyboardEvent<HTMLTextAreaElement>) => {
if (e.metaKey && e.key === "Enter") {
submitButtonRef.current?.click();
}
},
[]
);
return (
<Card className="p-2 rounded-3xl shadow">
<form onSubmit={props.onSubmit} className="grid grid-cols-1 gap-2">
{props.children}
<AutosizeTextarea
value={props.inputValue}
placeholder="Type a message..."
className="resize-none"
variant={"ghost"}
onChange={props.onInputChange}
maxHeight={260}
onKeyDown={handleKeyDown}
/>
<ChatActions>
{props.onFileChange && (
<>
<DropdownMenu>
<DropdownMenuTrigger>
<ChatActionIconButton>
<LucidePlus />
</ChatActionIconButton>
</DropdownMenuTrigger>
<DropdownMenuContent>
<DropdownMenuItem
onClick={() => fileInputRef.current?.click()}
>
Upload from computer
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
<input
ref={fileInputRef}
onChange={props.onFileChange}
type="file"
className="hidden"
/>
</>
)}
{props.actions}
<div className="flex-1" />
<Button
ref={submitButtonRef}
type="submit"
size="icon"
className="rounded-full"
>
<LucideArrowUp />
</Button>
</ChatActions>
</form>
</Card>
);
};
export { ChatForm };
2. Writing registry.json
Create a registry.json file at the project root and configure it using the documentation as a reference. The key points are as follows:
- List npm package dependencies under dependencies
- List shadcn/ui CLI-compatible dependencies under registryDependencies
With registryDependencies
, you can manage dependencies on shadcn/ui components and your own shadcn/ui CLI compatible components.
{
"$schema": "https://ui.shadcn.com/schema/registry.json",
"name": "Agents Kit",
"homepage": "https://agents-kit.dev",
"items": [
...
{
"name": "chat-form",
"type": "registry:component",
"title": "Chat Form",
"description": "A chat form component.",
"dependencies": ["lucide-react"],
"registryDependencies": [
"card",
"dropdown-menu",
"https://agents-kit.dev/r/autosize-textarea.json",
"https://agents-kit.dev/r/chat-actions.json"
],
"files": [
{
"path": "src/registry/agents-kit/chat-form.tsx",
"type": "registry:component",
"target": "components/agents-kit/chat-form.tsx"
}
]
},
...
]
}
3. Build component.json
Run the shadcn build
command to build the component.json file. In this example, public/r/chat-form.json
is generated.
{
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
"name": "chat-form",
"type": "registry:component",
"title": "Chat Form",
"description": "A chat form component.",
"dependencies": [
"lucide-react"
],
"registryDependencies": [
"card",
"dropdown-menu",
"https://agents-kit.dev/r/autosize-textarea.json",
"https://agents-kit.dev/r/chat-actions.json"
],
"files": [
{
"path": "src/registry/agents-kit/chat-form.tsx",
"content": "\"use client\";\n\n ...",
"type": "registry:component",
"target": "components/agents-kit/chat-form.tsx"
}
]
}
Once the project is deployed and JSON file is published, you can run a command as the following:
npx shadcn@latest add http://agents-kit.dev/r/chat-form.json
4. Developing React project as a documentation website
This completes the distribution process! Since you need to deploy the project anyway to publish the component.json, I recommend developing your React project as the documentation site.
Highlights
Streamlined Dependency Management
The registry.json file manages npm packages and other component.json dependencies, simplifying development and distribution. Dependencies are automatically installed, allowing you to distribute components without publishing them on npm.
Effortless Team Collaboration
By bypassing npm publishing, you can quickly share and maintain a component library within your team. This approach offers all the essential features while being more convenient than alternatives like npm or GitHub Packages.
Top comments (1)
You did it, Keita M! ๐
Your first article is live, and we couldnโt be more excited! Keep the creativity flowing, and letโs build something wonderful together. โ๏ธ๐