Styling & Theme
ความแตกต่างที่พื้นฐานที่สุดระหว่างการทำงาน UI ของ React กับ Flutter คือสิ่งนี้ ไม่มี stylesheet ไม่มีไฟล์ CSS ไม่มี className ไม่มี styled-components ไม่มี utility class ของ Tailwind การทำ styling ใน Flutter ถูกแสดงออกผ่าน property ของ widget constructor ทั้งหมด ทั้ง color, typography, spacing และ decoration ล้วนเป็น Dart object ที่ซ้อนอยู่ภายใน widget tree
สำหรับ design token ระดับทั้งแอป เช่น สีของแบรนด์ scale ของ text รูปทรงของปุ่ม Flutter ใช้ ThemeData ซึ่งคุณตั้งค่าครั้งเดียวที่ MaterialApp(theme: ...) แล้วเรียกใช้จากที่ไหนก็ได้ผ่าน Theme.of(context) นี่คือสิ่งที่เทียบเท่าโดยตรงกับ ThemeProvider ที่ห่อแอป React ของคุณ
เปรียบเทียบแบบเคียงข้างกัน
หัวข้อที่มีชื่อว่า “เปรียบเทียบแบบเคียงข้างกัน”// React — CSS-in-JS / styled-components style
// 1. Inline styles (like Flutter widget props)<p style={{ fontSize: 18, color: '#1a1a1a', fontWeight: 700 }}> Hello</p>
// 2. styled-components (design token from ThemeProvider)const Heading = styled.h1` font-size: ${({ theme }) => theme.fontSizes.xl}px; color: ${({ theme }) => theme.colors.primary}; font-weight: 700;`;
// 3. ThemeProvider wrapping the app<ThemeProvider theme={myTheme}> <App /></ThemeProvider>
// 4. Consuming the theme in a componentconst { theme } = useTheme();<div style={{ background: theme.colors.surface }}>...</div>// Flutter — widget properties + ThemeData
// 1. Inline TextStyle (equivalent to inline styles)Text( 'Hello', style: TextStyle( fontSize: 18, color: Color(0xFF1A1A1A), fontWeight: FontWeight.bold, ),)
// 2. BoxDecoration (background, border, radius, shadow)Container( decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(12), border: Border.all(color: Colors.grey[300]!), ), child: ...,)
// 3. ThemeData wrapping the app (in MaterialApp)MaterialApp( theme: ThemeData( colorScheme: ColorScheme.fromSeed(seedColor: Colors.blue), textTheme: const TextTheme( headlineMedium: TextStyle(fontWeight: FontWeight.bold), ), ), home: const MyHome(),)
// 4. Consuming the theme anywherefinal theme = Theme.of(context);Container(color: theme.colorScheme.surface, ...)ตัวอย่างที่รันได้
หัวข้อที่มีชื่อว่า “ตัวอย่างที่รันได้”import 'package:flutter/material.dart';
void main() {
runApp(
MaterialApp(
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: const Color(0xFF0468D7)),
useMaterial3: true,
textTheme: const TextTheme(
headlineSmall: TextStyle(fontWeight: FontWeight.bold, fontSize: 22),
bodyMedium: TextStyle(fontSize: 15, height: 1.6),
),
),
home: const StyleDemo(),
),
);
}
class StyleDemo extends StatelessWidget {
const StyleDemo({super.key});
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
final cs = theme.colorScheme;
return Scaffold(
backgroundColor: cs.surfaceContainerLow,
appBar: AppBar(
backgroundColor: cs.primary,
title: Text('Styling & Theme',
style: TextStyle(color: cs.onPrimary, fontWeight: FontWeight.bold)),
),
body: SingleChildScrollView(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
spacing: 16,
children: [
// TextStyle from theme
Text('Headline from ThemeData',
style: theme.textTheme.headlineSmall),
Text('Body text with theme line-height',
style: theme.textTheme.bodyMedium),
// TextStyle override via copyWith
Text('Local color override',
style: theme.textTheme.bodyMedium!
.copyWith(color: cs.error, fontWeight: FontWeight.w600)),
// BoxDecoration card
Container(
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
color: cs.primaryContainer,
borderRadius: BorderRadius.circular(16),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
spacing: 8,
children: [
Text('Primary container card',
style: TextStyle(color: cs.onPrimaryContainer,
fontWeight: FontWeight.bold, fontSize: 16)),
Text('Colours from ColorScheme.fromSeed',
style: TextStyle(color: cs.onPrimaryContainer)),
],
),
),
// BoxDecoration with gradient
Container(
height: 80,
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [cs.primary, cs.tertiary],
),
borderRadius: BorderRadius.circular(12),
),
alignment: Alignment.center,
child: Text('Gradient via BoxDecoration',
style: TextStyle(color: cs.onPrimary,
fontWeight: FontWeight.bold)),
),
// Buttons inherit theme automatically
Row(
spacing: 8,
children: [
ElevatedButton(onPressed: () {}, child: const Text('Elevated')),
TextButton(onPressed: () {}, child: const Text('Text')),
OutlinedButton(onPressed: () {}, child: const Text('Outlined')),
],
),
],
),
),
);
}
}