Theming
A DiagramTheme decides how a canvas looks: its background and grid, selection chrome, the styles that new shapes and connectors start with, how authored colors paint in dark mode, and whether it is light or dark. It is composed of small sub-themes, one per concern. The chrome an extension adds, such as the minimap, takes its colors from that extension's options, with a light or dark default chosen by the theme's brightness.
dart
DiagramCanvas(
controller: controller,
theme: DiagramTheme.dark,
)Install a theme
There are two places to set it; the canvas wins over the host theme.
| Where | Scope |
|---|---|
ThemeData(extensions: [DiagramTheme.dark]) | Every canvas below the host Theme. DiagramTheme is a ThemeExtension, so it follows MaterialApp's theme/darkTheme switch. |
DiagramCanvas(theme: ...) | One canvas and the chrome its extensions add around it, such as the editor's toolbar and inspector. |
Without either the canvas uses DiagramTheme.light, the default look. Read the active theme anywhere below a canvas with DiagramTheme.of(context).
dart
MaterialApp(
theme: ThemeData(extensions: const [DiagramTheme.light]),
darkTheme: ThemeData.dark().copyWith(
extensions: const [DiagramTheme.dark],
),
home: DiagramCanvas(controller: controller),
);Presets
| Preset | Look |
|---|---|
DiagramTheme.light | The default canvas. Documents paint exactly in their authored colors. |
DiagramTheme.dark | A dark canvas with brighter selection chrome. Authored colors are adapted so that dark strokes and text stay readable; see dark mode. |
Start from a preset and replace what you need with copyWith:
dart
final brand = DiagramTheme.light.copyWith(
canvas: const CanvasTheme(background: Color(0xFFF7F7F2)),
selection: const SelectionTheme(
color: Color(0xFF0A7D5A),
marquee: Color(0xFF0A7D5A),
),
);Themes interpolate with lerp, so a host Theme animates between light and dark as it does for Material colors.
Sub-themes
| Sub-theme | Field | Controls |
|---|---|---|
CanvasTheme | canvas | background; gridMinor and gridMajor override the document grid's colors when set. DiagramTheme(canvas: CanvasTheme(background: color)) sets the canvas color. |
SelectionTheme | selection | Selection and hover outlines (color), handleFill, handleHover, the marquee and the selected-text highlight while editing. |
ShapeTheme | shapes | Styles for new shapes, how authored shape and text colors paint (adapt), the collapseControl glyphs of frames and the eraser's erasePreview fade. |
ConnectorTheme | connectors | The style of new connectors, how authored connector and label colors paint (adapt), connection feedback (accepted, rejected) and the bundleBadge fill. |
Styles for new content
ShapeTheme and ConnectorTheme give content created on the canvas its starting style: shapes drawn with a tool, dropped from a palette, created by connected creation, and shapes and connectors added through the controller while the canvas is mounted.
dart
DiagramTheme(
shapes: ShapeTheme(
style: ShapeStyle(fill: DiagramColor(0xFFF8FAFC)),
types: {
Shapes.card: ShapeStyle(fill: DiagramColor(0xFFFFF4CC)),
},
),
connectors: ConnectorTheme(
style: ConnectorStyle(color: DiagramColor(0xFF334155), strokeWidth: 1.5),
),
)typesstyles one shape type;stylecovers every other type. Leave both out to keep each shape type's own default.- A style the user picks for future shapes (for example in the inspector while a creation tool is active) takes precedence over the theme.
- These are starting styles only. They are written into the document when the content is created, and changing the theme later does not restyle existing content.
Dark mode
A document stores its own colors, and most documents are drawn for a light background: black strokes and text, white fills. On a dark canvas those colors would disappear, so ShapeTheme.adapt and ConnectorTheme.adapt re-map authored colors while painting:
| Adaptation | Effect |
|---|---|
ColorAdaptation.none | Colors paint as authored. Used by DiagramTheme.light. |
ColorAdaptation.dark | Flips lightness and keeps hue, saturation and opacity: black ink turns near-white and white fills become dark surfaces, while a red stays red. Used by DiagramTheme.dark. |
Adaptation changes only what the canvas and minimap show. The document keeps its authored colors, so saving, collaboration and undo are unaffected, and the same document looks right in both themes. Images and shape shadows are not adapted.
Exports stay in authored colors. To render a PNG as the dark canvas shows it, pass the theme to exportDiagramPng(publication, options: ..., theme: DiagramTheme.dark).
For a different mapping, subclass ColorAdaptation and implement call. It runs while painting, so keep it fast and free of side effects.
dart
final class Grayscale extends ColorAdaptation {
const Grayscale();
@override
Color call(Color color) {
final l = color.computeLuminance();
return Color.from(alpha: color.a, red: l, green: l, blue: l);
}
}Extension themes
Extensions that add chrome to the canvas own its colors in their own options. Each takes an optional theme in its constructor; without one it picks its light or dark default by the canvas DiagramTheme.brightness, so installing the extension is enough for a light or a dark canvas:
| Theme | Set with | Controls |
|---|---|---|
MinimapTheme | MinimapExtension(theme: ...) (MinimapOptions.theme) | Minimap surface and visible-area outline, and the zoom panel's background, border, shadow, icons, text and control states. |
EditorTheme | EditorExtension(theme: ...) (EditorOptions.theme) | The editor's chrome, in two parts: ToolbarTheme and InspectorTheme. |
SnapGuideTheme | SnapGuidesExtension(theme: ...) (SnapGuidesOptions.theme) | Snapline color. |
TableTheme | TableFlutterExtension(theme: ...) (TableFlutterOptions.theme) | The table controls over the canvas: the range actions' surface and text, the row and column controls, and the selected cell-range color, which follows the canvas selection color when absent. |
EditorTheme holds the colors of each part of the editor:
| Part | Controls |
|---|---|
EditorTheme.toolbar (ToolbarTheme) | The floating toolbar, the tool palette and the history toolbar: surface, border, shadow, icons, the hairlines between groups, hover, and the active tool's background and icon. The active tool follows the canvas selection color unless selected and selectedIcon are set. |
EditorTheme.inspector (InspectorTheme) | The inspector panel and the shape libraries in it: surface, text, labels and section labels, fields, control outlines, dividers, hover, error text, the floating panel's shadow and corner radius, and the accent of switches, sliders and selected options, which follows the canvas selection color unless accent is set. |
The editor applies these colors to every control it builds, including menus and tooltips opened from its toolbars, so a light application can show a dark canvas with dark editor chrome, and the other way around. The defaults meet WCAG AA contrast for text and icons. Shape library thumbnails paint with the canvas theme's content adaptation, as the canvas paints the shapes they create.
Start a custom theme from the package's light or dark value:
dart
final controller = DiagramController(
configuration: DiagramConfiguration(
extensions: [
EditorExtension(
theme: EditorTheme.dark.copyWith(
inspector: InspectorTheme.dark.copyWith(accent: Colors.teal),
),
),
MinimapExtension(
theme: MinimapTheme.dark.copyWith(viewport: Colors.teal),
),
],
),
);
DiagramCanvas(controller: controller, theme: DiagramTheme.dark);Because the theme is part of the extension's options, it can change at runtime like any other option:
dart
controller.edits.setOptions(
controller.options<MinimapOptions>().copyWith(theme: MinimapTheme.light),
);Define one for your extension
Put the theme in your extension's options, and when it is null pick a light or dark default from DiagramTheme.of(context).brightness where the extension builds its chrome:
dart
import 'package:flutter/widgets.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:vyuh_diagram_flutter/extension_api.dart';
import 'package:vyuh_diagram_flutter/vyuh_diagram_flutter.dart';
@immutable
final class BadgeTheme {
const BadgeTheme({this.fill = const Color(0xFFFFD60A)});
static const light = BadgeTheme();
static const dark = BadgeTheme(fill: Color(0xFFB08900));
final Color fill;
@override
bool operator ==(Object other) => other is BadgeTheme && other.fill == fill;
@override
int get hashCode => fill.hashCode;
}
final class BadgesOptions {
const BadgesOptions({this.theme});
/// Badge colors, or null for the default by the canvas brightness.
final BadgeTheme? theme;
@override
bool operator ==(Object other) =>
other is BadgesOptions && other.theme == theme;
@override
int get hashCode => theme.hashCode;
}
final class BadgesExtension extends DiagramFlutterExtension {
BadgesExtension({BadgeTheme? theme})
: super(id: 'app.badges', options: BadgesOptions(theme: theme));
@override
Widget build(BuildContext context, DiagramCanvasExtensionHost host) =>
Observer(
builder: (context) {
final theme =
host.controller.options<BadgesOptions>().theme ??
(DiagramTheme.of(context).brightness == Brightness.dark
? BadgeTheme.dark
: BadgeTheme.light);
return Badges(host.controller.scene, fill: theme.fill);
},
);
}DiagramTheme itself has no list of extension themes: it holds the canvas, selection, shape and connector sub-themes and the brightness extensions follow.
Open the playground and use the toolbar's dark canvas toggle to see a light and dark canvas with the editor installed.