Files
monisuo/.agents/skills/shadcn-ui-flutter/SKILL.md
2026-03-23 00:08:19 +08:00

17 KiB
Raw Blame History

Shadcn UI for Flutter - 技能文档

Flutter 版本的 shadcn/ui 组件库,提供美观、可定制的 UI 组件

📦 安装

# pubspec.yaml
dependencies:
  shadcn_ui: ^0.2.4  # 使用最新版本
flutter pub add shadcn_ui

🚀 快速开始

纯 Shadcn UI

import 'package:shadcn_ui/shadcn_ui.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return ShadApp(
      home: MyHomePage(),
    );
  }
}

Shadcn + Material推荐

import 'package:shadcn_ui/shadcn_ui.dart';
import 'package:flutter/material.dart';
import 'package:flutter_localizations/flutter_localizations.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return ShadApp.custom(
      themeMode: ThemeMode.dark,
      darkTheme: ShadThemeData(
        brightness: Brightness.dark,
        colorScheme: const ShadSlateColorScheme.dark(),
      ),
      appBuilder: (context) {
        return MaterialApp(
          theme: Theme.of(context),
          localizationsDelegates: const [
            GlobalShadLocalizations.delegate,
            GlobalMaterialLocalizations.delegate,
            GlobalCupertinoLocalizations.delegate,
            GlobalWidgetsLocalizations.delegate,
          ],
          builder: (context, child) {
            return ShadAppBuilder(child: child!);
          },
          home: MyHomePage(),
        );
      },
    );
  }
}

🎨 主题配置

颜色方案

支持的颜色方案:

  • blue - 蓝色
  • gray - 灰色
  • green - 绿色
  • neutral - 中性色
  • orange - 橙色
  • red - 红色
  • rose - 玫瑰色
  • slate - 石板色(推荐深色模式)
  • stone - 石头色
  • violet - 紫罗兰色
  • yellow - 黄色
  • zinc - 锌色
ShadApp(
  darkTheme: ShadThemeData(
    brightness: Brightness.dark,
    colorScheme: const ShadSlateColorScheme.dark(),
  ),
  theme: ShadThemeData(
    brightness: Brightness.light,
    colorScheme: const ShadZincColorScheme.light(),
  ),
)

动态切换主题

final lightColorScheme = ShadColorScheme.fromName('blue');
final darkColorScheme = ShadColorScheme.fromName('slate', brightness: Brightness.dark);

自定义颜色

ShadThemeData(
  colorScheme: const ShadZincColorScheme.light(
    custom: {
      'brand': Color(0xFF00D4AA),
    },
  ),
)

// 访问自定义颜色
ShadTheme.of(context).colorScheme.custom['brand']!

📝 文本样式

// 标题
Text('Heading 1 Large', style: ShadTheme.of(context).textTheme.h1Large)
Text('Heading 1', style: ShadTheme.of(context).textTheme.h1)
Text('Heading 2', style: ShadTheme.of(context).textTheme.h2)
Text('Heading 3', style: ShadTheme.of(context).textTheme.h3)
Text('Heading 4', style: ShadTheme.of(context).textTheme.h4)

// 正文
Text('Paragraph', style: ShadTheme.of(context).textTheme.p)
Text('Lead', style: ShadTheme.of(context).textTheme.lead)
Text('Large', style: ShadTheme.of(context).textTheme.large)
Text('Small', style: ShadTheme.of(context).textTheme.small)
Text('Muted', style: ShadTheme.of(context).textTheme.muted)

// 其他
Text('Blockquote', style: ShadTheme.of(context).textTheme.blockquote)
Text('Table', style: ShadTheme.of(context).textTheme.table)
Text('List', style: ShadTheme.of(context).textTheme.list)

自定义字体

// pubspec.yaml
flutter:
  fonts:
    - family: CustomFont
      fonts:
        - asset: fonts/CustomFont-Regular.ttf

// 使用
ShadThemeData(
  textTheme: ShadTextTheme(
    family: 'CustomFont',
  ),
)

// 或使用 Google Fonts
ShadThemeData(
  textTheme: ShadTextTheme.fromGoogleFont(GoogleFonts.poppins),
)

🧩 核心组件

Button 按钮

// 主要按钮
ShadButton(
  child: const Text('Primary'),
  onPressed: () {},
)

// 次要按钮
ShadButton.secondary(
  child: const Text('Secondary'),
  onPressed: () {},
)

// 危险按钮
ShadButton.destructive(
  child: const Text('Delete'),
  onPressed: () {},
)

// 边框按钮
ShadButton.outline(
  child: const Text('Outline'),
  onPressed: () {},
)

// 幽灵按钮
ShadButton.ghost(
  child: const Text('Ghost'),
  onPressed: () {},
)

// 链接按钮
ShadButton.link(
  child: const Text('Link'),
  onPressed: () {},
)

// 带图标
ShadButton(
  leading: const Icon(LucideIcons.mail),
  child: const Text('Login with Email'),
  onPressed: () {},
)

// 加载状态
ShadButton(
  leading: SizedBox.square(
    dimension: 16,
    child: CircularProgressIndicator(
      strokeWidth: 2,
      color: ShadTheme.of(context).colorScheme.primaryForeground,
    ),
  ),
  child: const Text('Please wait'),
)

// 渐变和阴影
ShadButton(
  gradient: const LinearGradient(colors: [Colors.cyan, Colors.indigo]),
  shadows: [
    BoxShadow(
      color: Colors.blue.withOpacity(.4),
      spreadRadius: 4,
      blurRadius: 10,
      offset: const Offset(0, 2),
    ),
  ],
  child: const Text('Gradient'),
)

IconButton 图标按钮

ShadIconButton(
  icon: const Icon(LucideIcons.rocket),
  onPressed: () {},
)

ShadIconButton.secondary(
  icon: const Icon(LucideIcons.settings),
  onPressed: () {},
)

ShadIconButton.destructive(
  icon: const Icon(LucideIcons.trash),
  onPressed: () {},
)

Input 输入框

// 基础输入框
ShadInput(
  placeholder: const Text('Enter your email'),
)

// 表单输入框
ShadInputFormField(
  id: 'email',
  label: const Text('Email'),
  placeholder: const Text('Enter your email'),
  description: const Text('We will never share your email.'),
  validator: (value) {
    if (value == null || value.isEmpty) {
      return 'Email is required';
    }
    if (!value.contains('@')) {
      return 'Invalid email format';
    }
    return null;
  },
)

Card 卡片

ShadCard(
  width: 350,
  title: Text('Create project', style: ShadTheme.of(context).textTheme.h4),
  description: const Text('Deploy your new project in one-click.'),
  footer: Row(
    mainAxisAlignment: MainAxisAlignment.spaceBetween,
    children: [
      ShadButton.outline(
        child: const Text('Cancel'),
        onPressed: () {},
      ),
      ShadButton(
        child: const Text('Deploy'),
        onPressed: () {},
      ),
    ],
  ),
  child: Padding(
    padding: const EdgeInsets.symmetric(vertical: 16),
    child: Column(
      children: [
        const Text('Name'),
        const SizedBox(height: 6),
        const ShadInput(placeholder: Text('Project name')),
      ],
    ),
  ),
)

Alert 警告

// 普通警告
ShadAlert(
  icon: Icon(LucideIcons.terminal),
  title: Text('Heads up!'),
  description: Text('You can add components using the cli.'),
)

// 错误警告
ShadAlert.destructive(
  icon: Icon(LucideIcons.circleAlert),
  title: Text('Error'),
  description: Text('Your session has expired.'),
)

Badge 徽章

ShadBadge(child: const Text('Primary'))
ShadBadge.secondary(child: const Text('Secondary'))
ShadBadge.destructive(child: const Text('Destructive'))
ShadBadge.outline(child: const Text('Outline'))

Checkbox 复选框

bool checked = false;

ShadCheckbox(
  value: checked,
  onChanged: (v) => setState(() => checked = v),
  label: const Text('Accept terms and conditions'),
  sublabel: const Text('You agree to our Terms of Service.'),
)

// 表单中使用
ShadCheckboxFormField(
  id: 'terms',
  initialValue: false,
  inputLabel: const Text('I accept the terms'),
  validator: (v) {
    if (!v) return 'You must accept the terms';
    return null;
  },
)

Switch 开关

bool enabled = false;

ShadSwitch(
  value: enabled,
  onChanged: (v) => setState(() => enabled = v),
)

// 表单中使用
ShadSwitchFormField(
  id: 'notifications',
  initialValue: false,
  inputLabel: const Text('Enable notifications'),
)

Select 选择器

final options = ['Option 1', 'Option 2', 'Option 3'];
String? selected;

ShadSelect<String>(
  placeholder: const Text('Select an option'),
  options: options
      .map((e) => ShadOption(value: e, child: Text(e)))
      .toList(),
  selectedOptionBuilder: (context, value) {
    return Text(value);
  },
  onChanged: (value) {
    setState(() => selected = value);
  },
)

// 表单中使用
ShadSelectFormField<String>(
  id: 'framework',
  label: const Text('Framework'),
  placeholder: const Text('Select'),
  options: [
    ShadOption(value: 'flutter', child: Text('Flutter')),
    ShadOption(value: 'react', child: Text('React')),
  ],
  selectedOptionBuilder: (context, value) => Text(value),
)

Dialog 对话框

// 显示对话框
showShadDialog(
  context: context,
  builder: (context) => ShadDialog(
    title: const Text('Edit Profile'),
    description: const Text('Make changes to your profile.'),
    actions: [
      ShadButton.outline(
        child: const Text('Cancel'),
        onPressed: () => Navigator.of(context).pop(),
      ),
      ShadButton(
        child: const Text('Save'),
        onPressed: () => Navigator.of(context).pop(),
      ),
    ],
    child: Container(
      width: 375,
      padding: const EdgeInsets.all(20),
      child: Column(
        children: [
          ShadInput(placeholder: Text('Name')),
        ],
      ),
    ),
  ),
)

// 警告对话框
showShadDialog(
  context: context,
  builder: (context) => ShadDialog.alert(
    title: const Text('Are you sure?'),
    description: const Text('This action cannot be undone.'),
    actions: [
      ShadButton.outline(
        child: const Text('Cancel'),
        onPressed: () => Navigator.of(context).pop(false),
      ),
      ShadButton.destructive(
        child: const Text('Delete'),
        onPressed: () => Navigator.of(context).pop(true),
      ),
    ],
  ),
)

DatePicker 日期选择器

DateTime? selected;

ShadDatePicker(
  selected: selected,
  onChanged: (date) {
    setState(() => selected = date);
  },
)

// 日期范围选择器
ShadDatePicker.range()

// 表单中使用
ShadDatePickerFormField(
  id: 'birthday',
  label: const Text('Date of birth'),
  description: const Text('Your date of birth.'),
  validator: (v) {
    if (v == null) return 'Date of birth is required';
    return null;
  },
)

Calendar 日历

DateTime selected = DateTime.now();

ShadCalendar(
  selected: selected,
  fromMonth: DateTime(2024, 1),
  toMonth: DateTime(2024, 12),
)

// 多选
ShadCalendar.multiple(
  numberOfMonths: 2,
  min: 5,
  max: 10,
)

// 范围选择
ShadCalendar.range(
  min: 2,
  max: 5,
)

Avatar 头像

ShadAvatar(
  'https://example.com/avatar.jpg',
  placeholder: Text('CN'),
)

Accordion 手风琴

final details = [
  (title: 'Is it acceptable?', content: 'Yes.'),
  (title: 'Is it styled?', content: 'Yes.'),
];

ShadAccordion<({String content, String title})>(
  children: details.map(
    (detail) => ShadAccordionItem(
      value: detail,
      title: Text(detail.title),
      child: Text(detail.content),
    ),
  ),
)

// 多开模式
ShadAccordion<({String content, String title})>.multiple(
  children: details.map(...),
)

Breadcrumb 面包屑

ShadBreadcrumb(
  children: [
    ShadBreadcrumbLink(
      onPressed: () => print('Home'),
      child: const Text('Home'),
    ),
    ShadBreadcrumbLink(
      onPressed: () => print('Components'),
      child: const Text('Components'),
    ),
    Text('Breadcrumb'),
  ],
)

// 带下拉菜单
ShadBreadcrumb(
  children: [
    ShadBreadcrumbLink(
      onPressed: () {},
      child: const Text('Home'),
    ),
    ShadBreadcrumbDropdown(
      items: [
        ShadBreadcrumbDropMenuItem(
          onPressed: () {},
          child: const Text('Documentation'),
        ),
        ShadBreadcrumbDropMenuItem(
          onPressed: () {},
          child: const Text('Themes'),
        ),
      ],
      child: const Text('Components'),
    ),
    Text('Breadcrumb'),
  ],
)

Context Menu 右键菜单

ShadContextMenuRegion(
  items: [
    const ShadContextMenuItem(child: Text('Copy')),
    const ShadContextMenuItem(child: Text('Cut')),
    const ShadContextMenuItem(child: Text('Paste')),
    const Divider(height: 8),
    const ShadContextMenuItem(child: Text('Delete')),
  ],
  child: Container(
    width: 300,
    height: 200,
    child: const Text('Right click here'),
  ),
)

📋 表单管理

基础表单

final formKey = GlobalKey<ShadFormState>();

ShadForm(
  key: formKey,
  child: Column(
    children: [
      ShadInputFormField(
        id: 'username',
        label: const Text('Username'),
        validator: (v) {
          if (v == null || v.isEmpty) return 'Username is required';
          if (v.length < 3) return 'Username must be at least 3 characters';
          return null;
        },
      ),
      ShadInputFormField(
        id: 'email',
        label: const Text('Email'),
        validator: (v) {
          if (v == null || !v.contains('@')) return 'Invalid email';
          return null;
        },
      ),
      ShadButton(
        child: const Text('Submit'),
        onPressed: () {
          if (formKey.currentState!.saveAndValidate()) {
            print('Form value: ${formKey.currentState!.value}');
          }
        },
      ),
    ],
  ),
)

初始值

ShadForm(
  initialValue: {
    'username': 'john_doe',
    'email': 'john@example.com',
  },
  child: Column(...),
)

嵌套表单(点号表示法)

ShadForm(
  child: Column(
    children: [
      ShadInputFormField(
        id: 'user.name',
        label: const Text('Name'),
      ),
      ShadInputFormField(
        id: 'user.email',
        label: const Text('Email'),
      ),
    ],
  ),
)

// 输出: {'user': {'name': '...', 'email': '...'}}

🎯 图标

使用 Lucide Icons

Icon(LucideIcons.home)
Icon(LucideIcons.settings)
Icon(LucideIcons.user)
Icon(LucideIcons.search)
Icon(LucideIcons.mail)
Icon(LucideIcons.heart)
Icon(LucideIcons.star)

浏览所有图标:https://lucide.dev/icons/


🎬 动画

所有组件都支持 flutter_animate 动画:

ShadButton(
  child: const Text('Animated Button'),
  onPressed: () {},
  // 添加动画效果
).animate().fadeIn().slideX()

📦 依赖库

shadcn_ui 包含以下优秀的库:

  1. flutter_animate - 动画库
  2. lucide_icons_flutter - 图标库
  3. two_dimensional_scrollables - 表格组件
  4. intl - 国际化
  5. universal_image - 图片加载

💡 最佳实践

1. 与 Material 混用

// 推荐:使用 ShadApp.custom 包装 MaterialApp
ShadApp.custom(
  appBuilder: (context) => MaterialApp(
    theme: Theme.of(context),
    builder: (context, child) => ShadAppBuilder(child: child!),
    home: MyPage(),
  ),
)

2. 主题定制

ShadThemeData(
  colorScheme: const ShadZincColorScheme.dark(),
  primaryButtonTheme: const ShadButtonTheme(
    backgroundColor: Color(0xFF00D4AA),
  ),
  textTheme: ShadTextTheme(
    family: 'CustomFont',
  ),
)

3. 表单验证

ShadInputFormField(
  id: 'password',
  label: const Text('Password'),
  obscureText: true,
  validator: (v) {
    if (v == null || v.isEmpty) return 'Password is required';
    if (v.length < 8) return 'Password must be at least 8 characters';
    return null;
  },
)

🚨 注意事项

  1. 必须使用 ShadApp:不要直接使用 MaterialApp要使用 ShadApp 或 ShadApp.custom
  2. 主题访问:使用 ShadTheme.of(context) 访问主题
  3. 图标:使用 LucideIcons 而不是 Material Icons
  4. 表单:推荐使用 ShadForm 配合各种 FormField 组件

📚 参考资源


🎨 示例主题配置

深色主题(推荐)

ShadApp(
  themeMode: ThemeMode.dark,
  darkTheme: ShadThemeData(
    brightness: Brightness.dark,
    colorScheme: const ShadSlateColorScheme.dark(),
  ),
  home: MyApp(),
)

浅色主题

ShadApp(
  themeMode: ThemeMode.light,
  theme: ShadThemeData(
    brightness: Brightness.light,
    colorScheme: const ShadZincColorScheme.light(),
  ),
  home: MyApp(),
)

自定义品牌色

ShadThemeData(
  colorScheme: const ShadZincColorScheme.dark(
    custom: {
      'brand': Color(0xFF00D4AA),
      'success': Color(0xFF10B981),
      'warning': Color(0xFFF59E0B),
      'error': Color(0xFFEF4444),
    },
  ),
)

// 使用
Container(
  color: ShadTheme.of(context).colorScheme.custom['brand'],
)

文档版本: 0.2.4 最后更新: 2026-03-22