17 KiB
17 KiB
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 包含以下优秀的库:
- flutter_animate - 动画库
- lucide_icons_flutter - 图标库
- two_dimensional_scrollables - 表格组件
- intl - 国际化
- 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;
},
)
🚨 注意事项
- 必须使用 ShadApp:不要直接使用 MaterialApp,要使用 ShadApp 或 ShadApp.custom
- 主题访问:使用
ShadTheme.of(context)访问主题 - 图标:使用
LucideIcons而不是 Material Icons - 表单:推荐使用 ShadForm 配合各种 FormField 组件
📚 参考资源
- 官方文档:https://mariuti.com/flutter-shadcn-ui/
- LLM 友好文档:https://mariuti.com/flutter-shadcn-ui/llms.txt
- GitHub:https://github.com/nank1ro/flutter-shadcn-ui
- Lucide 图标:https://lucide.dev/icons/
🎨 示例主题配置
深色主题(推荐)
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