Skip to main content
Glama

genui

Generate React, Vue, or HTML UI component code from descriptions to accelerate frontend development and maintain consistent design patterns.

Instructions

【UI 组件生成器】生成 React/Vue UI 组件代码

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
descriptionNo组件描述
frameworkNo框架:react, vue, html(默认 react)

Implementation Reference

  • The primary handler function for the 'genui' tool. It takes arguments including 'description' and 'framework' (default 'react'), constructs a comprehensive multilingual prompt for generating high-quality, accessible UI components with examples, best practices, and code snippets for React, Vue, or HTML, and returns a structured content response or error.
    export async function genui(args: any) { try { const description = args?.description || ""; const framework = args?.framework || "react"; // react, vue, html const message = `请生成以下 UI 组件: 📝 **组件描述**: ${description || "请描述需要的 UI 组件"} ⚛️ **框架**:${framework} --- ## UI 组件生成指南 ### 第一步:理解需求 **组件类型**: - 基础组件(Button, Input, Card) - 表单组件(Form, Select, Checkbox) - 数据展示(Table, List, Grid) - 反馈组件(Modal, Toast, Loading) - 导航组件(Menu, Tabs, Breadcrumb) - 布局组件(Layout, Container, Flex) ### 第二步:设计原则 **1️⃣ 组件化** - 单一职责 - 可复用 - 可组合 **2️⃣ 可访问性(A11y)** - 语义化 HTML - ARIA 属性 - 键盘导航 - 屏幕阅读器支持 **3️⃣ 响应式** - 移动端优先 - 断点设计 - 弹性布局 **4️⃣ 性能** - 懒加载 - 虚拟滚动 - 防抖节流 --- ## React 组件示例 ### 基础 Button 组件 \`\`\`tsx import React from 'react'; import { cva, type VariantProps } from 'class-variance-authority'; const buttonVariants = cva( // 基础样式 "inline-flex items-center justify-center rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50", { variants: { variant: { default: "bg-blue-600 text-white hover:bg-blue-700", destructive: "bg-red-600 text-white hover:bg-red-700", outline: "border border-gray-300 bg-transparent hover:bg-gray-100", ghost: "hover:bg-gray-100", link: "text-blue-600 underline-offset-4 hover:underline", }, size: { default: "h-10 px-4 py-2", sm: "h-9 px-3", lg: "h-11 px-8", icon: "h-10 w-10", }, }, defaultVariants: { variant: "default", size: "default", }, } ); export interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement>, VariantProps<typeof buttonVariants> { isLoading?: boolean; } const Button = React.forwardRef<HTMLButtonElement, ButtonProps>( ({ className, variant, size, isLoading, children, ...props }, ref) => { return ( <button className={buttonVariants({ variant, size, className })} ref={ref} disabled={isLoading || props.disabled} {...props} > {isLoading && ( <svg className="mr-2 h-4 w-4 animate-spin" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" > <circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4" /> <path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z" /> </svg> )} {children} </button> ); } ); Button.displayName = "Button"; export { Button, buttonVariants }; \`\`\` **使用示例:** \`\`\`tsx <Button variant="default" size="lg"> 提交 </Button> <Button variant="outline" isLoading> 加载中... </Button> <Button variant="ghost" size="icon"> <Icon /> </Button> \`\`\` --- ### 表单输入组件 \`\`\`tsx import React from 'react'; import { useController, Control } from 'react-hook-form'; interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> { label?: string; error?: string; helperText?: string; control?: Control<any>; name: string; } export const Input = React.forwardRef<HTMLInputElement, InputProps>( ({ label, error, helperText, className, control, name, ...props }, ref) => { const { field } = useController({ name, control, defaultValue: props.defaultValue || '', }); return ( <div className="w-full space-y-2"> {label && ( <label htmlFor={name} className="text-sm font-medium text-gray-700" > {label} {props.required && <span className="text-red-500 ml-1">*</span>} </label> )} <input {...field} {...props} ref={ref} id={name} aria-invalid={!!error} aria-describedby={error ? \`\${name}-error\` : undefined} className={\` w-full px-3 py-2 border rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 disabled:bg-gray-100 disabled:cursor-not-allowed \${error ? 'border-red-500' : 'border-gray-300'} \${className || ''} \`} /> {error && ( <p id={\`\${name}-error\`} className="text-sm text-red-600"> {error} </p> )} {helperText && !error && ( <p className="text-sm text-gray-500">{helperText}</p> )} </div> ); } ); Input.displayName = 'Input'; \`\`\` --- ### Modal 弹窗组件 \`\`\`tsx import React, { useEffect } from 'react'; import { createPortal } from 'react-dom'; interface ModalProps { isOpen: boolean; onClose: () => void; title?: string; children: React.ReactNode; size?: 'sm' | 'md' | 'lg' | 'xl'; } export const Modal: React.FC<ModalProps> = ({ isOpen, onClose, title, children, size = 'md', }) => { // ESC 关闭 useEffect(() => { const handleEsc = (e: KeyboardEvent) => { if (e.key === 'Escape') onClose(); }; if (isOpen) { document.addEventListener('keydown', handleEsc); document.body.style.overflow = 'hidden'; } return () => { document.removeEventListener('keydown', handleEsc); document.body.style.overflow = 'unset'; }; }, [isOpen, onClose]); if (!isOpen) return null; const sizeClasses = { sm: 'max-w-md', md: 'max-w-lg', lg: 'max-w-2xl', xl: 'max-w-4xl', }; return createPortal( <div className="fixed inset-0 z-50 flex items-center justify-center" onClick={onClose} > {/* 背景遮罩 */} <div className="absolute inset-0 bg-black/50 backdrop-blur-sm" /> {/* 弹窗内容 */} <div className={\` relative bg-white rounded-lg shadow-xl w-full mx-4 \${sizeClasses[size]} max-h-[90vh] flex flex-col animate-in fade-in slide-in-from-bottom-4 \`} onClick={(e) => e.stopPropagation()} role="dialog" aria-modal="true" aria-labelledby={title ? 'modal-title' : undefined} > {/* 头部 */} {title && ( <div className="px-6 py-4 border-b"> <h2 id="modal-title" className="text-xl font-semibold"> {title} </h2> </div> )} {/* 关闭按钮 */} <button onClick={onClose} className="absolute top-4 right-4 text-gray-400 hover:text-gray-600" aria-label="关闭" > <svg className="w-6 h-6" fill="none" viewBox="0 0 24 24" stroke="currentColor"> <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" /> </svg> </button> {/* 内容 */} <div className="px-6 py-4 overflow-y-auto flex-1"> {children} </div> </div> </div>, document.body ); }; \`\`\` --- ### Table 数据表格 \`\`\`tsx import React from 'react'; interface Column<T> { key: keyof T | string; title: string; render?: (value: any, record: T, index: number) => React.ReactNode; width?: string; align?: 'left' | 'center' | 'right'; } interface TableProps<T> { columns: Column<T>[]; data: T[]; rowKey: keyof T; loading?: boolean; onRowClick?: (record: T) => void; } export function Table<T extends Record<string, any>>({ columns, data, rowKey, loading, onRowClick, }: TableProps<T>) { if (loading) { return <div className="text-center py-8">加载中...</div>; } if (data.length === 0) { return <div className="text-center py-8 text-gray-500">暂无数据</div>; } return ( <div className="overflow-x-auto"> <table className="min-w-full divide-y divide-gray-200"> <thead className="bg-gray-50"> <tr> {columns.map((col, index) => ( <th key={String(col.key) || index} style={{ width: col.width }} className={\` px-6 py-3 text-xs font-medium text-gray-500 uppercase tracking-wider text-\${col.align || 'left'} \`} > {col.title} </th> ))} </tr> </thead> <tbody className="bg-white divide-y divide-gray-200"> {data.map((record, rowIndex) => ( <tr key={String(record[rowKey])} onClick={() => onRowClick?.(record)} className={onRowClick ? 'cursor-pointer hover:bg-gray-50' : ''} > {columns.map((col, colIndex) => { const value = col.key in record ? record[col.key as keyof T] : undefined; return ( <td key={colIndex} className={\`px-6 py-4 whitespace-nowrap text-sm text-\${col.align || 'left'}\`} > {col.render ? col.render(value, record, rowIndex) : String(value)} </td> ); })} </tr> ))} </tbody> </table> </div> ); } \`\`\` --- ## Tailwind CSS 配置 \`\`\`js // tailwind.config.js module.exports = { content: ['./src/**/*.{js,ts,jsx,tsx}'], theme: { extend: { keyframes: { 'fade-in': { from: { opacity: 0 }, to: { opacity: 1 }, }, 'slide-in-from-bottom': { from: { transform: 'translateY(1rem)' }, to: { transform: 'translateY(0)' }, }, }, animation: { 'fade-in': 'fade-in 0.2s ease-out', 'slide-in-from-bottom-4': 'slide-in-from-bottom 0.3s ease-out', }, }, }, plugins: [], }; \`\`\` --- ## Vue 3 组件示例 ### 基础 Button 组件 (Vue 3 + TypeScript) \`\`\`vue <template> <button :class="buttonClasses" :disabled="isLoading || disabled" v-bind="$attrs" > <svg v-if="isLoading" class="mr-2 h-4 w-4 animate-spin" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" > <circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4" /> <path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z" /> </svg> <slot /> </button> </template> <script setup lang="ts"> import { computed } from 'vue'; interface Props { variant?: 'default' | 'destructive' | 'outline' | 'ghost' | 'link'; size?: 'default' | 'sm' | 'lg' | 'icon'; isLoading?: boolean; disabled?: boolean; } const props = withDefaults(defineProps<Props>(), { variant: 'default', size: 'default', isLoading: false, disabled: false, }); const buttonClasses = computed(() => { const base = 'inline-flex items-center justify-center rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50'; const variants = { default: 'bg-blue-600 text-white hover:bg-blue-700', destructive: 'bg-red-600 text-white hover:bg-red-700', outline: 'border border-gray-300 bg-transparent hover:bg-gray-100', ghost: 'hover:bg-gray-100', link: 'text-blue-600 underline-offset-4 hover:underline', }; const sizes = { default: 'h-10 px-4 py-2', sm: 'h-9 px-3', lg: 'h-11 px-8', icon: 'h-10 w-10', }; return \`\${base} \${variants[props.variant]} \${sizes[props.size]}\`; }); </script> \`\`\` **使用示例:** \`\`\`vue <template> <div> <Button variant="default" size="lg">提交</Button> <Button variant="outline" :is-loading="true">加载中...</Button> <Button variant="ghost" size="icon"> <Icon /> </Button> </div> </template> <script setup lang="ts"> import Button from '@/components/Button.vue'; </script> \`\`\` --- ### Vue 3 Input 组件 \`\`\`vue <template> <div class="w-full space-y-2"> <label v-if="label" :for="inputId" class="text-sm font-medium text-gray-700" > {{ label }} <span v-if="required" class="text-red-500 ml-1">*</span> </label> <input :id="inputId" v-model="model" :type="type" :placeholder="placeholder" :required="required" :disabled="disabled" :aria-invalid="!!error" :aria-describedby="error ? \`\${inputId}-error\` : undefined" :class="inputClasses" v-bind="$attrs" /> <p v-if="error" :id="\`\${inputId}-error\`" class="text-sm text-red-600"> {{ error }} </p> <p v-else-if="helperText" class="text-sm text-gray-500"> {{ helperText }} </p> </div> </template> <script setup lang="ts"> import { computed, useAttrs } from 'vue'; interface Props { modelValue?: string | number; label?: string; type?: string; placeholder?: string; error?: string; helperText?: string; required?: boolean; disabled?: boolean; } const props = withDefaults(defineProps<Props>(), { type: 'text', required: false, disabled: false, }); const emit = defineEmits<{ 'update:modelValue': [value: string | number]; }>(); const attrs = useAttrs(); const inputId = computed(() => attrs.id as string || \`input-\${Math.random().toString(36).slice(2)}\`); const model = computed({ get: () => props.modelValue, set: (value) => emit('update:modelValue', value), }); const inputClasses = computed(() => { return \` w-full px-3 py-2 border rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 disabled:bg-gray-100 disabled:cursor-not-allowed \${props.error ? 'border-red-500' : 'border-gray-300'} \`; }); </script> \`\`\` --- ### Vue 3 Modal 组件 \`\`\`vue <template> <Teleport to="body"> <Transition name="modal"> <div v-if="modelValue" class="fixed inset-0 z-50 flex items-center justify-center" @click="handleClose" > <!-- 背景遮罩 --> <div class="absolute inset-0 bg-black/50 backdrop-blur-sm" /> <!-- 弹窗内容 --> <div :class="modalClasses" @click.stop role="dialog" aria-modal="true" :aria-labelledby="title ? 'modal-title' : undefined" > <!-- 头部 --> <div v-if="title" class="px-6 py-4 border-b"> <h2 id="modal-title" class="text-xl font-semibold"> {{ title }} </h2> </div> <!-- 关闭按钮 --> <button @click="handleClose" class="absolute top-4 right-4 text-gray-400 hover:text-gray-600" aria-label="关闭" > <svg class="w-6 h-6" fill="none" viewBox="0 0 24 24" stroke="currentColor"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" /> </svg> </button> <!-- 内容 --> <div class="px-6 py-4 overflow-y-auto flex-1"> <slot /> </div> </div> </div> </Transition> </Teleport> </template> <script setup lang="ts"> import { computed, watch } from 'vue'; import { onKeyStroke } from '@vueuse/core'; interface Props { modelValue: boolean; title?: string; size?: 'sm' | 'md' | 'lg' | 'xl'; } const props = withDefaults(defineProps<Props>(), { size: 'md', }); const emit = defineEmits<{ 'update:modelValue': [value: boolean]; }>(); const handleClose = () => { emit('update:modelValue', false); }; // ESC 关闭 onKeyStroke('Escape', () => { if (props.modelValue) { handleClose(); } }); // 锁定 body 滚动 watch(() => props.modelValue, (isOpen) => { document.body.style.overflow = isOpen ? 'hidden' : ''; }); const modalClasses = computed(() => { const sizeClasses = { sm: 'max-w-md', md: 'max-w-lg', lg: 'max-w-2xl', xl: 'max-w-4xl', }; return \` relative bg-white rounded-lg shadow-xl w-full mx-4 \${sizeClasses[props.size]} max-h-[90vh] flex flex-col \`; }); </script> <style scoped> .modal-enter-active, .modal-leave-active { transition: opacity 0.3s ease; } .modal-enter-from, .modal-leave-to { opacity: 0; } </style> \`\`\` --- ## 组件库推荐 ### 🎨 React UI 组件库 - **shadcn/ui** - 可定制的 React 组件 - **Radix UI** - 无样式的可访问组件 - **Headless UI** - Tailwind 官方无样式组件 - **Ant Design** - 企业级 UI 设计语言 - **Material-UI** - Google Material Design ### 🎨 Vue UI 组件库 - **Element Plus** - 基于 Vue 3 的企业级组件库 - **Naive UI** - 完整的 TypeScript 支持 - **Ant Design Vue** - Vue 版本的 Ant Design - **Vuetify** - Material Design 组件框架 - **PrimeVue** - 丰富的 UI 组件集合 ### 🎭 动画库 - **Framer Motion** - React 动画库 - **React Spring** - 基于物理的动画 - **GSAP** - 高性能动画引擎(React/Vue 通用) - **@vueuse/motion** - Vue 3 组合式动画 ### 📱 响应式工具 - **Tailwind CSS** - 实用优先的 CSS 框架 - **UnoCSS** - 即时原子化 CSS 引擎 - **CSS Modules** - 局部作用域 CSS --- 现在请根据需求生成完整的 UI 组件代码,包括: 1. 组件实现(TypeScript) 2. 样式(Tailwind CSS) 3. 使用示例 4. Props 接口定义 5. 可访问性说明 **根据框架选择:** - **React**: 使用 Hooks、forwardRef、TypeScript - **Vue 3**: 使用 Composition API、script setup、TypeScript - **HTML**: 使用原生 JavaScript 和 Web Components`; return { content: [ { type: "text", text: message, }, ], }; } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); return { content: [ { type: "text", text: `❌ 生成 UI 组件失败: ${errorMessage}`, }, ], isError: true, }; } }
  • The input schema definition for the 'genui' tool in the MCP ListTools response, specifying name, description, and inputSchema with optional 'description' (string) and 'framework' (string, defaults to 'react').
    { name: "genui", description: "【UI 组件生成器】生成 React/Vue UI 组件代码", inputSchema: { type: "object", properties: { description: { type: "string", description: "组件描述", }, framework: { type: "string", description: "框架:react, vue, html(默认 react)", }, }, required: [], }, },
  • src/index.ts:510-511 (registration)
    Registration of the 'genui' tool in the MCP CallToolRequestSchema handler switch statement, dispatching calls to the genui function.
    case "genui": return await genui(args);
  • Re-export of the genui function from './genui.js' for barrel export in tools index, enabling import in src/index.ts.
    export { genui } from "./genui.js";
  • src/index.ts:14-14 (registration)
    Import statement including 'genui' from './tools/index.js' alongside other tools.
    fix, gensql, resolveConflict, genui, explain, convert, genreadme, split, analyzeProject

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/mybolide/mcp-probe-kit'

If you have feedback or need assistance with the MCP directory API, please join our Discord server