<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Patrick Botkins</title>
    <description>The latest articles on DEV Community by Patrick Botkins (@patrick_botkins_9aac0a7c5).</description>
    <link>https://dev.to/patrick_botkins_9aac0a7c5</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3862531%2F3eab5cc3-d7f2-4333-9153-25db4ce54b05.png</url>
      <title>DEV Community: Patrick Botkins</title>
      <link>https://dev.to/patrick_botkins_9aac0a7c5</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/patrick_botkins_9aac0a7c5"/>
    <language>en</language>
    <item>
      <title>Why AI coding tools generate ugly Flutter apps (and how to fix it)</title>
      <dc:creator>Patrick Botkins</dc:creator>
      <pubDate>Sun, 05 Apr 2026 16:49:48 +0000</pubDate>
      <link>https://dev.to/patrick_botkins_9aac0a7c5/why-ai-coding-tools-generate-ugly-flutter-apps-and-how-to-fix-it-3a9c</link>
      <guid>https://dev.to/patrick_botkins_9aac0a7c5/why-ai-coding-tools-generate-ugly-flutter-apps-and-how-to-fix-it-3a9c</guid>
      <description>&lt;p&gt;Ask Claude Code, Cursor, or Copilot to "build me a settings page in Flutter" and you already know what you'll get: a &lt;code&gt;ListTile&lt;/code&gt; inside a &lt;code&gt;Scaffold&lt;/code&gt; inside a &lt;code&gt;Card&lt;/code&gt;, styled with whatever &lt;code&gt;ThemeData.light()&lt;/code&gt; hands out. It'll work. It'll also look exactly like every other Flutter tutorial from 2019.&lt;/p&gt;

&lt;p&gt;This isn't the model's fault. It's a problem of defaults, training data, and missing context — and once you understand the failure mode, it's surprisingly easy to fix.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this happens
&lt;/h2&gt;

&lt;p&gt;Large language models produce code by sampling patterns they've seen. For Flutter, "patterns they've seen" means millions of GitHub repos, StackOverflow answers, and official docs — the overwhelming majority of which use:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;Card&lt;/code&gt;, &lt;code&gt;ListTile&lt;/code&gt;, &lt;code&gt;AppBar&lt;/code&gt; with default elevation&lt;/li&gt;
&lt;li&gt;Hardcoded &lt;code&gt;EdgeInsets.all(16)&lt;/code&gt; spacing&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Colors.blue&lt;/code&gt;, &lt;code&gt;Colors.grey[300]&lt;/code&gt;, &lt;code&gt;TextStyle(fontSize: 14)&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Material 3 primary/secondary tokens straight out of the box&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When you prompt an AI to "build a settings page," it samples the most statistically likely Flutter code for that request. The most likely answer is the most generic answer. That's how you end up with apps that technically work but look like they were built by the same intern who built the last five.&lt;/p&gt;

&lt;p&gt;There's a second, subtler issue. The model has no awareness of &lt;em&gt;your&lt;/em&gt; design system. It doesn't know your spacing scale, your type ramp, your radius values, your shadow treatments. Even when you ask nicely — "make it look modern" or "use soft shadows" — the model has to guess, and guessing produces inconsistency across screens.&lt;/p&gt;

&lt;h2&gt;
  
  
  The real fix: design tokens as a contract
&lt;/h2&gt;

&lt;p&gt;Before you reach for any AI tooling, the right abstraction for this problem is &lt;strong&gt;design tokens&lt;/strong&gt;. If you've worked in a mature design system, you already know the pattern: instead of writing &lt;code&gt;EdgeInsets.all(16)&lt;/code&gt; you write &lt;code&gt;EdgeInsets.all(LustreTokens.spacing16)&lt;/code&gt;. Instead of &lt;code&gt;Color(0xFF3B82F6)&lt;/code&gt; you write &lt;code&gt;LustreTokens.accentPrimary&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The value isn't aesthetic. It's that tokens turn design decisions into a &lt;strong&gt;contract&lt;/strong&gt; that any code — human or AI — can reference.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;LustreTokens&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Spacing scale (8pt grid)&lt;/span&gt;
  &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;spacing4&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;4.0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;spacing8&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;8.0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;spacing16&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;16.0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;spacing24&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;24.0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;spacing32&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;32.0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="c1"&gt;// Radius scale&lt;/span&gt;
  &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;radiusSm&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;8.0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;radiusMd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;12.0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;radiusLg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;20.0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="c1"&gt;// Type weights&lt;/span&gt;
  &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;FontWeight&lt;/span&gt; &lt;span class="n"&gt;fontRegular&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;FontWeight&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;w400&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;FontWeight&lt;/span&gt; &lt;span class="n"&gt;fontMedium&lt;/span&gt;   &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;FontWeight&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;w500&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;FontWeight&lt;/span&gt; &lt;span class="n"&gt;fontSemibold&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;FontWeight&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;w600&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="c1"&gt;// Animation&lt;/span&gt;
  &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;Duration&lt;/span&gt; &lt;span class="n"&gt;durationFast&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Duration&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;milliseconds:&lt;/span&gt; &lt;span class="mi"&gt;180&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;Curve&lt;/span&gt; &lt;span class="n"&gt;curveStandard&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Cubic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once tokens exist, every component in your app pulls from the same source. Change &lt;code&gt;spacing16&lt;/code&gt; to &lt;code&gt;18&lt;/code&gt; and the whole app breathes a little more. Change &lt;code&gt;radiusMd&lt;/code&gt; to &lt;code&gt;14&lt;/code&gt; and every card softens in lockstep.&lt;/p&gt;

&lt;p&gt;But here's the thing nobody writes about: &lt;strong&gt;this is exactly what AI coding tools need&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The missing piece: giving the AI your tokens
&lt;/h2&gt;

&lt;p&gt;The limitation of design tokens in isolation is that an AI assistant has no way to know they exist. You can paste them into context every time, but that's fragile, expensive in tokens, and falls apart the moment you switch to a new session or a new file.&lt;/p&gt;

&lt;p&gt;What AI coding tools need is a persistent way to look up your design system on demand. That's what the &lt;a href="https://modelcontextprotocol.io" rel="noopener noreferrer"&gt;Model Context Protocol&lt;/a&gt; (MCP) is for. An MCP server is a small process your AI tool can query — "give me the spacing scale," "give me the button component," "show me the dashboard layout" — and it returns real, consistent values pulled from a single source of truth.&lt;/p&gt;

&lt;h2&gt;
  
  
  Putting it together with Lustre
&lt;/h2&gt;

&lt;p&gt;I built &lt;a href="https://patrickbotkins.com/lustre" rel="noopener noreferrer"&gt;Lustre&lt;/a&gt; to solve this exact problem. It's two things:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A Flutter component library with 46+ premium components across 3 themes (Clean, Bold, Glass), all built on a strict token system.&lt;/li&gt;
&lt;li&gt;An MCP server (&lt;code&gt;lustre-mcp&lt;/code&gt; on npm) that exposes those components and tokens to any MCP-compatible AI tool.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Installing it takes one command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx lustre-mcp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then add it to your AI tool's MCP config (Claude Code, Cursor, and Codex all support this). After that, the AI has access to tools like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;list_components&lt;/code&gt; — the full catalog of available widgets&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;get_component&lt;/code&gt; — full source for any free component&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;search_components&lt;/code&gt; — find the right widget by intent&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;get_design_tokens&lt;/code&gt; — the complete token system&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;get_layout_pattern&lt;/code&gt; — pre-built screen layouts (settings, dashboard, profile, onboarding)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now when you ask Claude Code to "build a settings page," it doesn't guess. It looks up the settings layout pattern, pulls real token values, and produces code that already matches your design system.&lt;/p&gt;

&lt;h2&gt;
  
  
  A concrete example
&lt;/h2&gt;

&lt;p&gt;Here's the prompt:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Build me a Flutter settings page with a profile section, notification toggles, and an account section.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Without Lustre, you get the default: a &lt;code&gt;ListView&lt;/code&gt; of &lt;code&gt;ListTile&lt;/code&gt;s with a &lt;code&gt;Switch&lt;/code&gt; trailing, inside a &lt;code&gt;Scaffold&lt;/code&gt; with a basic &lt;code&gt;AppBar&lt;/code&gt;. It's functional and forgettable.&lt;/p&gt;

&lt;p&gt;With Lustre installed, the AI pulls &lt;code&gt;LustreSettingsPage&lt;/code&gt;, &lt;code&gt;LustreSettingsList&lt;/code&gt;, &lt;code&gt;LustreToggle&lt;/code&gt;, and the settings layout pattern. The result uses the spacing scale, respects the type ramp, has proper section dividers, supports light and dark mode automatically, and works in all three themes. The difference is not subtle.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why MCP is the right delivery mechanism
&lt;/h2&gt;

&lt;p&gt;You could solve this with a regular Flutter package, and plenty of projects do. But &lt;code&gt;pub add some_package&lt;/code&gt; doesn't change what the AI generates — it just adds code to your project that the AI has to discover. MCP inverts that: the components live &lt;em&gt;inside&lt;/em&gt; the AI's context, so they become the most likely thing to sample when you ask for UI.&lt;/p&gt;

&lt;p&gt;This is a meaningful shift. Design systems historically target humans. An MCP server targets the model.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this looks like in practice
&lt;/h2&gt;

&lt;p&gt;If you want to try it today, it's free to install and includes 15 core components (Button, TextField, AppBar, BottomNav, InfoCard, StatCard, Badge, Avatar, ProgressBar, Dialog, Snackbar, Toast, SearchBar, Toggle, Divider) with full source code. Additional layout patterns and specialized components (dashboards, e-commerce, settings suites) are available as paid kits if you want pre-built starter apps.&lt;/p&gt;

&lt;p&gt;The install:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# In your project&lt;/span&gt;
npx lustre-mcp

&lt;span class="c"&gt;# Then add to your Claude Code / Cursor / Codex MCP config&lt;/span&gt;
&lt;span class="c"&gt;# Full instructions: https://patrickbotkins.com/lustre&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No API keys. No accounts. No backend. It's a locally-run process that gives your AI coding assistant a design system to reference.&lt;/p&gt;

&lt;h2&gt;
  
  
  Takeaways
&lt;/h2&gt;

&lt;p&gt;If you're building Flutter apps with AI assistance and you don't love how they look, you have three levers:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Define design tokens.&lt;/strong&gt; Even if you never touch AI tooling, this is the single highest-leverage thing you can do for consistency. A good token system is a month of saved review cycles.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Make your tokens discoverable to the AI.&lt;/strong&gt; Pasting them into every prompt doesn't scale. An MCP server does.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use a component library built on those tokens.&lt;/strong&gt; Whether it's Lustre or something you build yourself, the AI generating &lt;em&gt;composed&lt;/em&gt; widgets beats the AI generating raw &lt;code&gt;Container&lt;/code&gt; + &lt;code&gt;Padding&lt;/code&gt; + &lt;code&gt;Text&lt;/code&gt; every time.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The stock-Material-defaults problem isn't going away on its own — the training data is what it is. But you can route around it with surprisingly little infrastructure, and the output quality jump is dramatic.&lt;/p&gt;

&lt;p&gt;If you want to see the full component catalog and all three themes side-by-side, the showcase is at &lt;a href="https://patrickbotkins.com/lustre" rel="noopener noreferrer"&gt;patrickbotkins.com/lustre&lt;/a&gt;. Feedback and issue reports are very welcome.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;I'm &lt;a href="https://twitter.com/theLightDelta" rel="noopener noreferrer"&gt;@theLightDelta&lt;/a&gt; on X. If you end up trying &lt;code&gt;lustre-mcp&lt;/code&gt;, I'd love to hear what you think — especially if you find something it gets wrong.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>flutter</category>
      <category>webdev</category>
      <category>productivity</category>
    </item>
  </channel>
</rss>
