Theme-owned controls
Buttons, badges, and surfaces inherit the active renderer.
ArcaneBox(
style: const ArcaneStyleData(
padding: PaddingPreset.lg,
background: Background.card,
border: BorderPreset.accent,
borderRadius: Radius.lg,
),
children: [
Button.primary(label: 'Primary', onPressed: () {}),
Button.secondary(label: 'Secondary', onPressed: () {}),
],
)
Theming#
Arcane Jaspr uses a stylesheet-based theming system built on CSS custom properties. Stylesheets provide complete design systems including colors, typography, spacing, and component renderers.
ArcaneApp#
Every Arcane application must be wrapped in an ArcaneApp component which provides the theme context and injects the stylesheet CSS.
import 'package:arcane_jaspr/arcane_jaspr.dart';
import 'package:arcane_jaspr_neon/arcane_jaspr_neon.dart';
import 'package:arcane_jaspr_neubrutalism/arcane_jaspr_neubrutalism.dart';
import 'package:arcane_jaspr_shadcn/arcane_jaspr_shadcn.dart';
ArcaneApp(
stylesheet: const ShadcnStylesheet(),
brightness: Brightness.dark,
home: YourApp(),
)
Available Stylesheets#
Arcane Jaspr keeps the Flutter-like authoring surface in package:arcane_jaspr and ships concrete stylesheets as renderer packages:
| Stylesheet | Description |
|---|---|
arcane_jaspr_shadcn / ShadcnStylesheet |
Quiet document/app chrome with compact spacing, subtle borders, and shadcn-style controls |
arcane_jaspr_neon / NeonStylesheet |
Command/workbench chrome with stronger contrast, denser shell treatment, larger controls, and bolder radius |
arcane_jaspr_neubrutalism / NeubrutalismStylesheet |
Comic-book pop-art chrome with thick black borders, hard-offset shadows, flat saturated colors, and press-down click feedback |
import 'package:arcane_jaspr_shadcn/arcane_jaspr_shadcn.dart';
ArcaneApp(
stylesheet: const ShadcnStylesheet(),
home: MyApp(),
)
import 'package:arcane_jaspr_neon/arcane_jaspr_neon.dart';
ArcaneApp(
stylesheet: const NeonStylesheet(),
home: MyApp(),
)
import 'package:arcane_jaspr_neubrutalism/arcane_jaspr_neubrutalism.dart';
ArcaneApp(
stylesheet: const NeubrutalismStylesheet(theme: NeubrutalismTheme.yellow),
home: MyApp(),
)
Brightness Mode#
Control light/dark mode with the brightness parameter:
ArcaneApp(
stylesheet: const ShadcnStylesheet(),
brightness: Brightness.dark,
home: MyApp(),
)
| Value | Description |
|---|---|
Brightness.dark | Dark mode (default) |
Brightness.light | Light mode |
CSS Variables#
Stylesheets define CSS custom properties that all components use. These are automatically injected into the document.
Core Colors#
--background /* Page background */
--foreground /* Default text color */
--card /* Card background */
--card-foreground /* Text on cards */
--popover /* Popover background */
--popover-foreground /* Text in popovers */
--primary /* Primary brand color */
--primary-foreground /* Text on primary */
--secondary /* Secondary color */
--secondary-foreground
--muted /* Muted backgrounds */
--muted-foreground /* Muted text */
--accent /* Accent color */
--accent-foreground
--destructive /* Error/danger color */
--destructive-foreground
--border /* Border color */
--input /* Input border color */
--ring /* Focus ring color */
Status Colors#
--success /* Success state */
--success-foreground
--warning /* Warning state */
--warning-foreground
--info /* Info state */
--info-foreground
Structure Tokens#
--radius /* Base border radius (e.g., 0.5rem) */
--font-sans /* Sans-serif font stack */
--font-mono /* Monospace font stack */
Arcane Aliases#
Stylesheets also define --arcane-* prefixed variables for package-level components:
--arcane-background
--arcane-foreground
--arcane-primary
--arcane-primary-foreground
--arcane-secondary
--arcane-accent
--arcane-muted
--arcane-border
--arcane-radius
--arcane-radius-sm
--arcane-radius-md
--arcane-radius-lg
/* ... and more */
Using CSS Variables in Styles#
Reference these variables in your ArcaneStyleData:
ArcaneBox(
style: const ArcaneStyleData(
raw: {
'background': 'var(--card)',
'border': '1px solid var(--border)',
'color': 'var(--card-foreground)',
'border-radius': 'var(--radius)',
},
),
children: [...],
)
Or use the type-safe presets which map to these variables:
ArcaneBox(
style: const ArcaneStyleData(
background: Background.card,
textColor: TextColor.cardForeground,
border: BorderPreset.standard,
borderRadius: Radius.md,
),
children: [...],
)
Component Renderers#
Each stylesheet provides its own implementation of all components via the renderer system. Access renderers through context:
@override
Widget build(BuildContext context) {
return Button(
label: 'Click me',
onPressed: () {},
);
}
Creating Custom Stylesheets#
To create a custom stylesheet, extend ArcaneStylesheet:
class MyStylesheet extends ArcaneStylesheet {
const MyStylesheet();
@override
String get id => 'my-style';
@override
String get name => 'My Style';
@override
ComponentRenderers get renderers => const MyRenderers();
@override
LayoutRenderers get layouts => const MyLayouts();
@override
String get baseCss => '''
:root {
--background: #ffffff;
--foreground: #000000;
--primary: #3b82f6;
--primary-foreground: #ffffff;
/* ... define all required variables */
}
.dark {
--background: #0a0a0a;
--foreground: #fafafa;
/* ... dark mode overrides */
}
''';
@override
get externalCssUrls => const [
'https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap',
];
}
Your custom MyRenderers class must implement all methods in ComponentRenderers, and
MyLayouts must implement adaptive layout slots such as ArcaneScaffold.
Theme Toggle#
Use ArcaneThemeToggle to let users switch between light and dark modes:
ArcaneThemeToggle(
isDark: true,
onChanged: (isDark) {
// Handle theme change
},
)
Static Site Support#
When building static sites with jaspr build, include fallback scripts for interactivity:
ArcaneApp(
stylesheet: const ShadcnStylesheet(),
includeFallbackScripts: true,
home: MyApp(),
)
This injects JavaScript that provides interactivity when Jaspr client hydration is unavailable.