smithery.ai

shadcn-flutter

Build modern Flutter applications using shadcn_flutter - a comprehensive UI component library with 84+ production-ready components including forms with type-safe validation, responsive theming, dialogs, tables, color pickers, and more.

First seen Apr 14, 2026

Installation

$ npx skills add https://smithery.ai

Summary

  • Build modern Flutter applications using shadcn_flutter - a comprehensive UI component library with 84+ production-ready components including forms with type-safe validation, responsive theming, dialogs, tables, color pickers, and more.
  • Covers proper app setup, semantic component naming, extension methods for styling, and Flutter-specific patterns for building dashboards, mobile apps, and web interfaces.

Similar popular skills

Related neighbors and high-traction skills in the same topics — useful to compare before installing.

Also in this package

Other skills from smithery.ai · top by installs.

npx skills add https://smithery.ai

Browse all from smithery.ai

More details

Agent compatibility

Declared targets from SKILL.md / docs. Unmarked agents are not listed — the skill may still install via the CLI.

Claude Code Not declared
Cursor Not declared
Codex Not declared
GitHub Copilot Not declared
Windsurf Not declared
Gemini CLI Not declared
Cline Not declared
OpenCode Not declared

Package contents

Files included with this skill beyond the listing page.

  • skill md SKILL.md 17,449 B
  • docs SUMMARY.md 428 B

History

  1. First seen on skills.sh
  2. First recorded snapshot · 1 installs

SKILL.md

shadcn_flutter Skill

Purpose

This skill enables Claude to build complete, production-ready Flutter applications using the shadcn_flutter UI component library. Use this skill when users request Flutter UIs, mobile/web apps, dashboards, forms, or any Flutter development task that would benefit from a modern, customizable component library.

When to Use This Skill

  • User asks to build/create a Flutter application or widget
  • User mentions Flutter, Dart, or mobile/web app development
  • User requests UI components like buttons, forms, cards, dialogs, tables
  • User wants to implement themes, dark mode, or responsive design
  • User needs form validation, color pickers, or data tables

Core Principles

1. Always Start with Proper App Setup

Every shadcn_flutter app needs the ShadcnApp wrapper with theme configuration:

import 'package:shadcn_flutter/shadcn_flutter.dart';

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

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

  @override
  Widget build(BuildContext context) {
    return ShadcnApp(
      theme: ThemeData(
        colorScheme: ColorSchemes.lightDefaultColor,
        radius: 0.5,
        scaling: 1,
        typography: const Typography.geist(),
      ),
      darkTheme: ThemeData.dark(
        colorScheme: ColorSchemes.darkDefaultColor,
        radius: 0.5,
      ),
      themeMode: ThemeMode.system,
      home: const HomePage(),
    );
  }
}

2. Use Semantic Component Names

shadcn_flutter uses clear, semantic names:

  • PrimaryButton, OutlineButton, GhostButton (NOT ElevatedButton)
  • Card with padding parameter (NOT Material or Container)
  • TextField with placeholder (NOT hintText)
  • Gap(16) for spacing (NOT SizedBox)

3. Leverage Extension Methods for Styling

Use fluent extension methods instead of TextStyle wrappers:

const Text('Title').semiBold().large()
const Text('Subtitle').muted().small()

Available extensions:

  • Text size: .small(), .large(), .xLarge()
  • Text weight: .semiBold(), .bold(), .light()
  • Text color: .muted(), .subtle()
  • Layout: .withPadding(), .sized(), .intrinsic()

4. Forms Are Type-Safe with Keys

Always use strongly-typed form keys and validation:

final _usernameKey = const TextFieldKey('username');

FormField(
  key: _usernameKey,
  label: const Text('Username'),
  validator: const LengthValidator(min: 4),
  child: const TextField(),
)

// In onSubmit:
String? username = _usernameKey[values];

Component Categories

Buttons

// Primary action
PrimaryButton(
  onPressed: () {},
  leading: const Icon(Icons.add),
  child: const Text('Add Item'),
)

// Secondary action
OutlineButton(
  onPressed: () {},
  child: const Text('Cancel'),
)

// Tertiary action
GhostButton(
  onPressed: () {},
  child: const Text('View Details'),
)

// Disabled state
PrimaryButton(
  onPressed: null,
  child: const Text('Disabled'),
)

Cards & Containers

Card(
  padding: const EdgeInsets.all(24),
  child: Column(
    crossAxisAlignment: CrossAxisAlignment.start,
    children: [
      const Text('Card Title').semiBold(),
      const Gap(4),
      const Text('Card description').muted().small(),
      const Gap(16),
      // Content here
    ],
  ),
).intrinsic() // Makes card fit content

Forms with Validation

class MyForm extends StatefulWidget {
  @override
  State<MyForm> createState() => _MyFormState();
}

class _MyFormState extends State<MyForm> {
  final _emailKey = const TextFieldKey('email');
  final _passwordKey = const TextFieldKey('password');

  @override
  Widget build(BuildContext context) {
    return Form(
      onSubmit: (context, values) {
        String? email = _emailKey[values];
        String? password = _passwordKey[values];
        // Handle submission
      },
      child: Column(
        children: [
          FormTableLayout(
            rows: [
              FormField(
                key: _emailKey,
                label: const Text('Email'),
                hint: const Text('Enter your email address'),
                validator: const EmailValidator(),
                child: const TextField(),
              ),
              FormField(
                key: _passwordKey,
                label: const Text('Password'),
                validator: const LengthValidator(min: 8),
                child: const TextField(obscureText: true),
              ),
            ],
          ),
          const Gap(24),
          FormErrorBuilder(
            builder: (context, errors, child) {
              return PrimaryButton(
                onPressed: errors.isEmpty ? () => context.submitForm() : null,
                child: const Text('Submit'),
              );
            },
          ),
        ],
      ),
    );
  }
}

Dialogs

showDialog(
  context: context,
  builder: (context) {
    return AlertDialog(
      title: const Text('Confirm Action'),
      content: const Text('Are you sure you want to proceed?'),
      actions: [
        OutlineButton(
          onPressed: () => Navigator.of(context).pop(false),
          child: const Text('Cancel'),
        ),
        PrimaryButton(
          onPressed: () => Navigator.of(context).pop(true),
          child: const Text('Confirm'),
        ),
      ],
    );
  },
);

Select Dropdowns

class MySelector extends StatefulWidget {
  @override
  State<MySelector> createState() => _MySelectorState();
}

class _MySelectorState extends State<MySelector> {
  String? selectedValue;

  @override
  Widget build(BuildContext context) {
    return Select<String>(
      itemBuilder: (context, item) => Text(item),
      onChanged: (value) => setState(() => selectedValue = value),
      value: selectedValue,
      placeholder: const Text('Select an option'),
      popup: const SelectPopup(
        items: SelectItemList(
          children: [
            SelectItemButton(value: 'option1', child: Text('Option 1')),
            SelectItemButton(value: 'option2', child: Text('Option 2')),
            SelectItemButton(value: 'option3', child: Text('Option 3')),
          ],
        ),
      ),
    );
  }
}

Tabs

class TabbedView extends StatefulWidget {
  @override
  State<TabbedView> createState() => _TabbedViewState();
}

class _TabbedViewState extends State<TabbedView> {
  int index = 0;

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        Tabs(
          index: index,
          onChanged: (value) => setState(() => index = value),
          children: const [
            TabItem(child: Text('Tab 1')),
            TabItem(child: Text('Tab 2')),
            TabItem(child: Text('Tab 3')),
          ],
        ),
        const Gap(16),
        IndexedStack(
          index: index,
          children: [
            Card(child: const Text('Content 1')),
            Card(child: const Text('Content 2')),
            Card(child: const Text('Content 3')),
          ],
        ),
      ],
    );
  }
}

Tables

Table(
  rows: [
    // Header row
    TableRow(
      cells: [
        TableCell(
          child: const Text('Name').muted().semiBold().withPadding(all: 8),
        ),
        TableCell(
          child: const Text('Status').muted().semiBold().withPadding(all: 8),
        ),
        TableCell(
          child: const Text('Amount').muted().semiBold().withPadding(all: 8),
        ),
      ],
    ),
    // Data rows
    TableRow(
      cells: [
        TableCell(child: const Text('John Doe').withPadding(all: 8)),
        TableCell(child: const Text('Active').withPadding(all: 8)),
        TableCell(child: const Text('\$100').withPadding(all: 8)),
      ],
    ),
    // Footer row
    TableFooter(
      cells: [
        TableCell(
          columnSpan: 3,
          child: Row(
            children: [
              const Text('Total'),
              const Spacer(),
              const Text('\$100').semiBold(),
            ],
          ).withPadding(all: 8),
        ),
      ],
    ),
  ],
)

Toast Notifications

showToast(
  context: context,
  builder: (context, overlay) {
    return SurfaceCard(
      child: Basic(
        title: const Text('Success'),
        subtitle: const Text('Your changes have been saved'),
        trailing: PrimaryButton(
          size: ButtonSize.small,
          onPressed: overlay.close,
          child: const Text('Dismiss'),
        ),
      ),
    );
  },
  location: ToastLocation.bottomRight,
  duration: const Duration(seconds: 3),
);

Color Picker

class ColorSelector extends StatefulWidget {
  @override
  State<ColorSelector> createState() => _ColorSelectorState();
}

class _ColorSelectorState extends State<ColorSelector> {
  Color selectedColor = Colors.blue;

  @override
  Widget build(BuildContext context) {
    return ColorInput(
      value: selectedColor,
      onChanged: (color) => setState(() => selectedColor = color),
      enableEyeDropper: true,
      showLabel: true,
    );
  }
}

Toggle & Switch

class ToggleExample extends StatefulWidget {
  @override
  State<ToggleExample> createState() => _ToggleExampleState();
}

class _ToggleExampleState extends State<ToggleExample> {
  final controller = ToggleController(false);

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        ControlledToggle(
          controller: controller,
          child: const Text('Enable feature'),
        ),
        Switch(
          value: controller.value,
          onChanged: (value) => controller.value = value,
        ),
      ],
    );
  }
}

Theme Customization

Custom Theme

final customTheme = ThemeData(
  colorScheme: ColorSchemes.lightDefaultColor,
  radius: 0.5,        // Border radius multiplier
  scaling: 1,         // Size scaling factor
  typography: const Typography.geist(),
  surfaceOpacity: 0.8,
  surfaceBlur: 10.0,
);

// Access theme in widgets
final theme = Theme.of(context);
final primaryColor = theme.colorScheme.primary;
final borderRadius = theme.borderRadiusLg; // 16px at normal radius
final titleStyle = theme.typography.h1;

Responsive Scaling

AdaptiveScaler(
  scaling: AdaptiveScaler.defaultScaling(Theme.of(context)),
  child: YourWidget(),
)

// Custom scaling
AdaptiveScaler(
  scaling: const AdaptiveScaling.only(
    radiusScaling: 1.5,
    sizeScaling: 1.2,
    textScaling: 1.1,
  ),
  child: YourWidget(),
)

Best Practices

1. Structure Your Code

// Good: Clear widget hierarchy
class DashboardPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        _buildHeader(),
        const Gap(24),
        _buildContent(),
      ],
    );
  }

  Widget _buildHeader() {
    return Card(
      child: const Text('Dashboard').large().semiBold(),
    );
  }

  Widget _buildContent() {
    return Card(
      child: // Content
    );
  }
}

2. Use Stateful Widgets for Interactive Components

// Good: State management for forms, selects, toggles
class InteractiveForm extends StatefulWidget {
  @override
  State<InteractiveForm> createState() => _InteractiveFormState();
}

class _InteractiveFormState extends State<InteractiveForm> {
  String? selectedValue;
  bool isEnabled = false;

  @override
  Widget build(BuildContext context) {
    // Build UI with state
  }
}

3. Leverage FormTableLayout for Forms

// Good: Consistent form layout
FormTableLayout(
  rows: [
    FormField(
      key: key1,
      label: const Text('Field 1'),
      child: const TextField(),
    ),
    FormField(
      key: key2,
      label: const Text('Field 2'),
      child: const TextField(),
    ),
  ],
)

4. Handle Loading and Error States

class DataView extends StatelessWidget {
  final bool isLoading;
  final String? error;
  final List<Item> data;

  @override
  Widget build(BuildContext context) {
    if (isLoading) {
      return const Center(child: CircularProgressIndicator());
    }
    
    if (error != null) {
      return Card(
        child: Text('Error: $error').muted(),
      );
    }
    
    return ListView.builder(
      itemCount: data.length,
      itemBuilder: (context, index) => Card(
        child: Text(data[index].name),
      ),
    );
  }
}

Common Patterns

Master-Detail Layout

class MasterDetailView extends StatefulWidget {
  @override
  State<MasterDetailView> createState() => _MasterDetailViewState();
}

class _MasterDetailViewState extends State<MasterDetailView> {
  int? selectedIndex;

  @override
  Widget build(BuildContext context) {
    return Row(
      children: [
        // Master list
        Expanded(
          flex: 1,
          child: ListView.builder(
            itemCount: items.length,
            itemBuilder: (context, index) {
              return GhostButton(
                onPressed: () => setState(() => selectedIndex = index),
                child: Text(items[index].title),
              );
            },
          ),
        ),
        const Gap(16),
        // Detail view
        Expanded(
          flex: 2,
          child: selectedIndex != null
              ? Card(child: Text(items[selectedIndex!].details))
              : Card(child: const Text('Select an item').muted()),
        ),
      ],
    );
  }
}

Settings Screen

class SettingsScreen extends StatefulWidget {
  @override
  State<SettingsScreen> createState() => _SettingsScreenState();
}

class _SettingsScreenState extends State<SettingsScreen> {
  bool notificationsEnabled = true;
  String theme = 'system';

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        Card(
          child: Column(
            children: [
              const Text('Preferences').semiBold().large(),
              const Gap(16),
              Row(
                children: [
                  const Text('Enable Notifications'),
                  const Spacer(),
                  Switch(
                    value: notificationsEnabled,
                    onChanged: (value) {
                      setState(() => notificationsEnabled = value);
                    },
                  ),
                ],
              ),
              const Gap(8),
              Row(
                children: [
                  const Text('Theme'),
                  const Spacer(),
                  Select<String>(
                    value: theme,
                    onChanged: (value) => setState(() => theme = value!),
                    itemBuilder: (context, item) => Text(item),
                    popup: const SelectPopup(
                      items: SelectItemList(
                        children: [
                          SelectItemButton(value: 'light', child: Text('Light')),
                          SelectItemButton(value: 'dark', child: Text('Dark')),
                          SelectItemButton(value: 'system', child: Text('System')),
                        ],
                      ),
                    ),
                  ),
                ],
              ),
            ],
          ),
        ),
      ],
    );
  }
}

Quick Reference

Spacing

  • Gap(4) - Extra small
  • Gap(8) - Small
  • Gap(16) - Medium (default)
  • Gap(24) - Large
  • Gap(32) - Extra large

Validators

  • LengthValidator(min: 4, max: 20)
  • EmailValidator()
  • CompareWith.equal(otherKey, message: '...')
  • Custom: Implement FormFieldValidator<T>

Button Sizes

  • ButtonSize.small
  • ButtonSize.medium (default)
  • ButtonSize.large

Toast Locations

  • ToastLocation.topLeft
  • ToastLocation.topCenter
  • ToastLocation.topRight
  • ToastLocation.bottomLeft
  • ToastLocation.bottomCenter
  • ToastLocation.bottomRight

Implementation Checklist

When building a shadcn_flutter app, ensure:

  1. ✅ App wrapped in ShadcnApp with theme configuration
  2. ✅ Import statement: import 'package:shadcnflutter/shadcnflutter.dart';
  3. ✅ Use semantic component names (PrimaryButton, not ElevatedButton)
  4. ✅ Forms use typed keys and FormTableLayout
  5. ✅ Spacing with Gap() instead of SizedBox
  6. ✅ Text styling with extension methods (.semiBold(), .muted())
  7. ✅ Cards use padding parameter
  8. ✅ TextFields use placeholder instead of decoration
  9. ✅ Proper state management with StatefulWidget where needed
  10. ✅ Theme access via Theme.of(context)

Summary

shadcn_flutter is a comprehensive UI library that prioritizes developer experience with semantic naming, type-safe forms, and extensive theming. Always start with proper app setup, use the library's semantic component names, leverage extension methods for quick styling, and implement type-safe forms with validation. The library works best when you embrace its conventions rather than fighting against them with Material/Cupertino patterns.