Songloft Color System Guide
This document explains the color system used in the Songloft project and the guidelines for using it.
📚 Table of Contents
- Flutter Material 3 Color System
- Theme Configuration
- Theme Pack System
- Color Usage Guidelines
- Responsive Theme Adaptation
Flutter Material 3 Color System
The Songloft Flutter frontend uses the Material 3 design system, generating a complete color scheme automatically via ColorScheme.fromSeed.
Core Configuration
// clients/player/lib/core/theme/app_theme.dart
class AppTheme {
static const Color _defaultSeedColor = Color(0xFF415F91); // M3 Blue baseline
static ThemeData lightTheme({
ScreenType screenType = ScreenType.mobile,
ThemePack? themePack, // Optional theme pack override
}) {
return _buildTheme(Brightness.light, screenType, themePack);
}
static ThemeData darkTheme({
ScreenType screenType = ScreenType.mobile,
ThemePack? themePack,
}) {
return _buildTheme(Brightness.dark, screenType, themePack);
}
}When a themePack is provided, the seedColor is replaced by the one defined in the theme pack, generating an entirely different palette.
Advantages
- Automatic palette generation: Generates complete light/dark color schemes automatically from a seed color
- Semantic roles: Semantic color roles such as
primary,secondary,tertiary,error - Guaranteed contrast: Material 3 automatically ensures text-to-background contrast meets accessibility standards
- Consistency: All components automatically use a unified color scheme
ColorScheme Color Roles
| Role | Purpose | Example |
|---|---|---|
primary | Primary actions, emphasis elements | Play button, selected navigation state |
onPrimary | Text/icons on top of primary | Button text |
primaryContainer | Primary-tinted container background | Selected card background |
secondary | Secondary actions | Auxiliary buttons |
tertiary | Third-level emphasis | Tags, badges |
error | Error states | Delete button, error messages |
surface | Page/card background | Scaffold background |
onSurface | Text on top of surface | Primary text |
onSurfaceVariant | Secondary text | Subtitles, descriptive text |
outline | Borders | Input field borders, dividers |
outlineVariant | De-emphasized borders | List dividers |
Theme Configuration
Theme Modes
Songloft supports three theme modes:
- Light mode: A bright interface style
- Dark mode: An eye-friendly dark interface
- Follow system: Automatically follows the operating system setting
Theme switching is implemented through the ThemeSelector component, with state managed by themeModeProvider.
Font Configuration
ThemeData(
fontFamilyFallback: const ['NotoSansSC', 'NotoSansKR', 'sans-serif'],
// ...
)- Uses the system font by default
- Chinese falls back to Noto Sans SC (bundled with the app)
- Korean falls back to Noto Sans KR (bundled with the app)
Component Theme Customization
ThemeData(
useMaterial3: true,
appBarTheme: const AppBarTheme(centerTitle: false, elevation: 0),
cardTheme: CardThemeData(elevation: 0, shape: RoundedRectangleBorder(...)),
inputDecorationTheme: InputDecorationTheme(border: OutlineInputBorder(...), filled: true),
navigationBarTheme: const NavigationBarThemeData(height: 64, ...),
// ...
)Theme Pack System
Songloft supports customizing the app’s color scheme and visual style via .songloft-theme theme packs. Theme mode (light/dark/system) and theme packs are independent — one theme pack defines both light and dark color schemes.
SongloftThemeExtension
Custom parameters specific to theme packs are injected via the ThemeExtension mechanism:
class SongloftThemeExtension extends ThemeExtension<SongloftThemeExtension> {
// Original fields
final List<Color>? playerGradientColors; // Player gradient colors
final double cardRadius; // Card border radius (default AppRadius.md)
final double controlRadius; // Control border radius (default AppRadius.md)
final double navigationRadius; // Navigation border radius (default AppRadius.md)
// Liquid Glass tokens
final Color glassFill; // Glass fill (light default 0xB8FFFFFF)
final Color glassFillStrong; // Strong glass fill (light default 0xD9FFFFFF)
final Color glassBorder; // Glass border (light default 0x73FFFFFF)
final Color glassHighlight; // Glass highlight (light default 0x99FFFFFF)
final Color glassGlow; // Glass primary glow (= glassBase, light default #3BAEEF)
final Color glassGlowFaint; // Faint glow (glassBase @ 0.10)
final Color glassSheen; // Sheen (glassBase @ 0.18)
final String navigationStyle; // Navigation bar style: 'standard' | 'capsule' (default 'standard')
}glassGlow / glassGlowFaint / glassSheen are all derived from glassBase: glassBase comes from ThemePackColors.glassColor; without a theme pack it falls back to #3BAEEF (light) / #5BC0F5 (dark).
Usage in components:
final ext = Theme.of(context).extension<SongloftThemeExtension>();
if (ext?.playerGradientColors != null) {
// Use the theme pack gradient
}
// Liquid Glass example
Container(
decoration: BoxDecoration(
color: ext?.glassFill,
border: Border.all(color: ext?.glassBorder ?? Colors.transparent),
),
)How Theme Packs Affect the Color System
When a user activates a theme pack:
- seedColor is replaced: A new full palette is generated from the theme pack’s
light.seedColor/dark.seedColor - surface/background can be overridden: If the theme pack specifies
backgroundColororsurfaceColor, theycopyWithoverride the auto-generated values - Player gradient overlay: Colors defined by
playerGradientare overlaid on the cover art’s dynamic colors at 40% opacity - Border radii:
cardRadius,controlRadius,navigationRadiusare injected into component themes - Liquid Glass tint:
glassColorserves as glassBase to deriveglassGlow/glassGlowFaint/glassSheen; light and dark can specify different values - Navigation bar style:
navigationStylecontrols the navigation bar appearance —'standard'(default indicator) or'capsule'(capsule bar)
Related Documents
- 📖 Theme Pack Development Guide — Detailed creation workflow and best practices
- 🎨 Online Theme Repository — Community theme packs
Color Usage Guidelines
✅ Recommended
1. Obtain Colors Through Theme
// Get the ColorScheme
final colorScheme = Theme.of(context).colorScheme;
// Primary color
Container(color: colorScheme.primary)
Text('Title', style: TextStyle(color: colorScheme.onSurface))
// Secondary text
Text('Description', style: TextStyle(color: colorScheme.onSurfaceVariant))
// Error state
Icon(Icons.error, color: colorScheme.error)2. Use TextTheme
final textTheme = Theme.of(context).textTheme;
Text('Large title', style: textTheme.headlineMedium)
Text('Body text', style: textTheme.bodyLarge)
Text('Caption', style: textTheme.bodySmall)3. Use the Built-in Colors of Material Components
// FilledButton automatically uses the primary color
FilledButton(onPressed: () {}, child: Text('Primary action'))
// OutlinedButton automatically uses the outline color
OutlinedButton(onPressed: () {}, child: Text('Secondary action'))
// TextButton automatically uses the primary color
TextButton(onPressed: () {}, child: Text('Text action'))❌ Avoid
// Do not hardcode color values
Container(color: Color(0xFF415F91)) // ❌
// Do not use Colors constants (they do not follow the theme)
Text('Text', style: TextStyle(color: Colors.grey)) // ❌
// Use Theme instead
Container(color: Theme.of(context).colorScheme.primary) // ✅
Text('Text', style: TextStyle(color: Theme.of(context).colorScheme.onSurfaceVariant)) // ✅Responsive Theme Adaptation
The theme dynamically adjusts component sizes based on screen type (Mobile / Tablet / Desktop):
SnackBar
| Screen Type | Style |
|---|---|
| Mobile | Default floating style |
| Desktop | Fixed width 480px, centered |
FilledButton / OutlinedButton / TextButton
| Screen Type | Minimum Size |
|---|---|
| Desktop | 88 × 44 |
| Mobile / Tablet | Flutter framework default (not customized) |
In the actual code (
app_theme.dart), all three offilledButtonTheme/outlinedButtonTheme/textButtonThemeare gated byisDesktop: the theme is only set for Desktop (Desktop → 88×44). For Mobile / Tablet they arenull, falling back to the Flutter framework default sizes.
Dialog Maximum Width
| Screen Type | Maximum Width |
|---|---|
| Mobile | 300px |
| Tablet | 400px |
| Desktop | 480px |
Cover Color Extraction
Songloft uses the palette_generator library to extract dominant colors from song cover images, used for dynamic coloring of the player interface:
// clients/player/lib/core/utils/color_extraction.dart
// Extract dominant colors from the cover image, applied to scenarios such as the player background gradientChangelog
- 2026-09-20: Liquid Glass theme system
SongloftThemeExtensionadds 8 Liquid Glass token fields (glassFill,glassFillStrong,glassBorder,glassHighlight,glassGlow,glassGlowFaint,glassSheen,navigationStyle)ThemePackColorsaddsglassColorfield, driving Liquid Glass tintThemePackaddsnavigationStylefield ('standard'/'capsule')- Font fallback adds NotoSansKR (Korean)
- 2026-07-31: Added theme pack system
- Support for
.songloft-themepacks to customize colors, radii, and player gradient - Added
SongloftThemeExtensioncustom theme extension AppTheme.lightTheme()/darkTheme()accept an optionalThemePackparameter- Online theme catalog: browse and install from songloft-themes
- Support for
- 2026-04-14: Migrated to the Flutter Material 3 color system
- Main frontend migrated to Flutter, using
ColorScheme.fromSeedfor automatic palette generation - seedColor: M3 Blue baseline (
#415F91) - Added responsive theme adaptation (Mobile / Tablet / Desktop)
- Added cover color extraction feature
- Main frontend migrated to Flutter, using
