dynamic_properties_panel
Schema-driven property editing panel for Flutter — with a companion
DynamicPropertiesController that enables granular rebuilds,
bidirectional sync between panel and rendered components, and
commit-only callbacks for expensive persistence layers.
Polished controls are included for primitives, enums, objects, arrays, maps, colors, icons, dates, durations, and more — plus widget-slot pickers, breadcrumb navigation, and preset toolbars.
Live demo
Try the production example: https://flutterdynamicpropertiespanel.netlify.app/
Production package notes
This standalone package uses the published color_picker_plus and json_view_plus packages instead of vendored local copies. Its bundled design-system snapshot is also pruned to the internal primitives the panel and example actually use, keeping the package smaller, easier to update, and aligned with the production-ready color and JSON controls used by the example app.
Installation
dependencies:
dynamic_properties_panel: ^1.0.0
import 'package:dynamic_properties_panel/dynamic_properties_panel.dart';
Two ways to use it
1. Controller-based (recommended)
Use this when you want:
- Granular rebuilds (changing one property only rebuilds its control)
- Live updates during drag without full-tree rebuilds
- Bidirectional sync — a rendered preview component writes back to the panel (Storybook-args pattern)
- Explicit save/persist without hidden serialization on every keystroke
import 'package:dynamic_properties_panel/dynamic_properties_panel.dart';
class MyPage extends StatefulWidget { ... }
class _MyPageState extends State<MyPage> {
late final DynamicPropertiesController _controller;
@override
void initState() {
super.initState();
_controller = DynamicPropertiesController(initial: loadedValues);
}
@override
void dispose() {
_controller.dispose(); // mandatory — caller owns lifecycle
super.dispose();
}
@override
Widget build(BuildContext context) {
return DynamicPropertiesPanel(
controller: _controller,
properties: DynamicPropertyDefinition.listFromJson(schema),
);
}
void _save() {
// Snapshot on demand — not synced on every change.
final values = _controller.snapshot();
persistToDisk(values);
}
}
2. Map-based (backwards-compatible)
The legacy constructor still works — the panel creates an internal
controller. onChanged now fires only on committed changes (drag end,
typing complete, preset applied), not on every drag tick:
DynamicPropertiesPanel(
values: values,
properties: DynamicPropertyDefinition.listFromJson(schema),
onChanged: (next) => setState(() => values = next),
)
DynamicPropertiesController API
// Construction
final controller = DynamicPropertiesController(initial: {'fontSize': 16});
// Reading
controller['fontSize']; // dynamic — null if never set
controller.notifierFor('fontSize'); // ValueNotifier<dynamic>
controller.hasValue('fontSize'); // bool — has it been explicitly set?
controller.snapshot(); // Map<String,dynamic> — deep-copied
controller.snapshotShallow(); // Map<String,dynamic> — cheap
// Writing — every write fires all listeners and the commits stream.
controller['fontSize'] = 18; // idiomatic
controller.setValue('fontSize', 18); // equivalent
controller.applyAll({'a': 1, 'b': 2}); // bulk
// Listening
controller.addListener(() { ... }); // any change
ValueListenableBuilder( // single property, granular
valueListenable: controller.notifierFor('enabled'),
builder: (context, value, _) => ...,
);
controller.commits.listen((change) { ... }); // every write (debounce if persisting)
// Lifecycle
controller.dispose(); // mandatory
Bidirectional sync (Storybook-args pattern)
The real payoff of the controller is that any widget — not just the panel — can bind to the same notifiers. Rendered preview components become participants in the shared state: the user can toggle a switch inside the live preview and the panel's corresponding row flips to match.
// The panel binds 'enabled' to its switch row automatically.
DynamicPropertiesPanel(controller: controller, properties: ...)
// Your preview component reads and writes the same state.
Widget previewSwitch(DynamicPropertiesController ctrl) {
final on = ctrl['enabled'] as bool? ?? false;
return ReboundSwitch(
value: on,
onChanged: (next) => ctrl['enabled'] = next, // panel's row flips to match
);
}
Persistence — debounce in your handler
Every write fires listeners and the commits stream, including on every
drag tick. Hosts that persist should debounce in their own handler:
Timer? saveTimer;
controller.commits.listen((_) {
saveTimer?.cancel();
saveTimer = Timer(const Duration(milliseconds: 300), () {
saveToDisk(controller.snapshot());
});
});
Granular rebuilds inside the panel mean the drag-tick firehose is cheap to observe — only the affected row rebuilds, not the whole panel or host.
Three listener tiers
| Mechanism | Scope | Best for |
|---|---|---|
controller.notifierFor(key) |
one property | per-field rebuilds in preview components |
controller.addListener(...) / ListenableBuilder(listenable: controller) |
all properties | live JSON display, debug panels, component preview |
controller.commits.listen(...) |
all writes, as PropertyChange events |
save-to-disk (with debounce), analytics |
Pick the tier that matches how expensive your consumer is.
Presets
Preset toolbars (save / reset / delete) are built in. Pass presets,
onPresetSelected, and onPresetsChanged on the panel — those work the
same in both controller and Map-based modes. Nested component presets use
componentPresets / onComponentPresetsChanged.
Property Types And Flutter Mapping
Use type in your schema to select a control. DynamicPropertyDefinition.inferKind
maps these strings to DynamicPropertyKind.
Core types
Schema type |
Kind | Stored value format | Flutter mapping |
|---|---|---|---|
String |
string |
String |
String properties |
int / integer |
integer |
int |
numeric integer props |
double / num / number |
double |
double |
numeric double props |
bool / boolean |
boolean |
bool |
toggle/boolean props |
any type + enumValues |
enumValue |
selected enum item | enum-like props (stored as listed value) |
any type + enumValues + multiSelect: true |
multiEnum |
List<dynamic> |
multi-select enum props |
object or properties |
object |
Map<String,dynamic> |
nested config objects |
List<...> / array / list |
array |
List<dynamic> |
list props |
Map<...> / map |
map |
Map<String,dynamic> |
key/value map props |
json / dynamic |
json |
any JSON-serializable value | raw fallback editor |
Flutter convenience types
Schema type |
Kind | Stored value format | Flutter mapping |
|---|---|---|---|
Color |
color |
#RRGGBB or #AARRGGBB |
Color |
Color.swatch / ColorSwatch |
colorSwatch |
#RRGGBB |
Color |
icon / IconData |
icon |
icon name String |
IconData via registry/picker |
Icon.swatch / IconSwatch |
iconSwatch |
icon name String |
IconData via swatch picker |
date / DateTime |
date |
ISO-8601 String |
DateTime |
Duration / duration |
duration |
int milliseconds |
Duration |
Slider |
slider |
double |
double range values |
Alignment / AlignmentGeometry |
alignment |
{x: double, y: double} |
Alignment |
EdgeInsets / EdgeInsetsGeometry |
edgeInsets |
{top,right,bottom,left} |
EdgeInsets |
BorderRadius / BorderRadiusGeometry |
borderRadius |
{topLeft,topRight,bottomLeft,bottomRight} |
BorderRadius |
BoxConstraints |
boxConstraints |
{minWidth,maxWidth,minHeight,maxHeight} |
BoxConstraints |
TextStyle |
textStyle |
{markdown: String, ...} |
TextStyle-adjacent rich text config |
MainAxisAlignment |
mainAxisAlignment |
enum name String (for example start) |
MainAxisAlignment |
CrossAxisAlignment |
crossAxisAlignment |
enum name String |
CrossAxisAlignment |
MainAxisSize |
mainAxisSize |
enum name String |
MainAxisSize |
Axis |
axis |
enum name String |
Axis |
TextAlign |
textAlign |
enum name String |
TextAlign |
Size |
size |
{width: double?, height: double?} |
Size |
Rotation |
rotation |
double degrees |
rotation angle props |
Widget slots
Schema type |
Kind | Stored value format |
|---|---|---|
Widget |
widget |
{componentId: String, config: Map<String,dynamic>} or null |
List<Widget> |
widgetList |
List<{componentId, config}?> |
Example schema
final properties = DynamicPropertyDefinition.listFromJson([
{'name': 'title', 'type': 'String'},
{'name': 'themeColor', 'type': 'Color'},
{'name': 'padding', 'type': 'EdgeInsets'},
{'name': 'layout', 'type': 'object', 'properties': {'radius': {'type': 'double'}}},
{'name': 'child', 'type': 'Widget'},
]);
Example
See /example for a complete app with:
- Nested object editing
- Map editing
- Widget and widget-list slot editing
- Live JSON preview (via
ListenableBuilder) - Interactive preview with bidirectional switch + slider
- Preset save/restore
- Widget-slot picker with child component config
Run it with flutter run from the /example directory.
Libraries
- dynamic_properties_panel
- Dynamic Properties Panel
- Soft SaaS UI Action Button Component
- soft_saas_ui/components/alignment_picker_inspector
- Soft SaaS UI Alignment Picker — Inspector-style right-panel layout.
- soft_saas_ui/components/axis_picker_inspector
- Inspector-style Axis (direction) picker — horizontal / vertical toggle chips.
- soft_saas_ui/components/badge
- Soft SaaS UI Badge Component
- soft_saas_ui/components/border_radius_picker_inspector
- Soft SaaS UI BorderRadius Picker — Inspector-style with mini preview.
- soft_saas_ui/components/box_constraints_picker_inspector
- Soft SaaS UI BoxConstraints Picker — Inspector-style with mini frame visual.
- Soft SaaS UI Button Components
- soft_saas_ui/components/checkbox
- Soft SaaS UI Checkbox Component
- soft_saas_ui/components/color_input
- Soft SaaS UI Color Input.
- soft_saas_ui/components/combo_input
- Soft SaaS UI Combo Input.
- soft_saas_ui/components/cross_axis_alignment_picker_inspector
- Inspector-style CrossAxisAlignment picker.
- soft_saas_ui/components/dropdown
- soft_saas_ui/components/dropdown_multiselect
- soft_saas_ui/components/edge_insets_picker_inspector
- Soft SaaS UI EdgeInsets Picker — Inspector-style with mini preview.
- soft_saas_ui/components/expandable
- Soft SaaS UI Expandable Component
- soft_saas_ui/components/icon_picker
- Soft SaaS UI Icon Picker.
- soft_saas_ui/components/icon_picker_inspector
- Soft SaaS UI Icon Picker — Inspector-style inline swatch grid.
- soft_saas_ui/components/inspector_toggle_row
- Generic Inspector-style icon toggle row.
- soft_saas_ui/components/main_axis_alignment_picker_inspector
- Inspector-style MainAxisAlignment picker.
- soft_saas_ui/components/main_axis_size_picker_inspector
- Inspector-style MainAxisSize picker — min / max toggle chips.
- soft_saas_ui/components/number_input
- Soft SaaS UI Number Input Component
- soft_saas_ui/components/number_preset_input
- Soft SaaS UI Number Preset Input Component
- soft_saas_ui/components/panel
- Soft SaaS UI Panel Component
- soft_saas_ui/components/reorderable_list
- Soft SaaS UI Reorderable List Component
- soft_saas_ui/components/resizable_row
- soft_saas_ui/components/rich_text_field
- Soft SaaS UI Rich Text Field.
- soft_saas_ui/components/rich_text_toolbar
- Soft SaaS UI Rich Text Toolbar — the kitchen sink of inline-formatting
buttons for SoftSaaSRichTextField /
TextfEditingController. - soft_saas_ui/components/rotation_picker_inspector
- Inspector-style Rotation picker — compact number input with ° suffix and a triangle dropdown exposing common angle presets (0/45/90/135/180/…). A small circle dial left of the input previews the current angle.
- soft_saas_ui/components/select
- soft_saas_ui/components/size_picker_inspector
- Soft SaaS UI Size Picker — Inspector-style with aspect-ratio indicator.
- soft_saas_ui/components/slider
- soft_saas_ui/components/switch
- Soft SaaS UI Switch Component
- soft_saas_ui/components/tabs
- soft_saas_ui/components/text_align_picker_inspector
- Inspector-style TextAlign picker — left / center / right / justify toggle chips.
- soft_saas_ui/components/text_input
- Soft SaaS UI Text Input Component
- soft_saas_ui/design_tokens
- Soft SaaS UI Design Tokens
- soft_saas_ui/icons/lucide_icon_registry
- Curated registry of Lucide icons for runtime name lookup.
- soft_saas_ui/neumorphic_shadows
- Neumorphic Shadow System for Soft SaaS UI
- soft_saas_ui/soft_saas_ui
- Minimal internal Soft SaaS UI surface used by dynamic_properties_panel.
- soft_saas_ui/theme
- Theme Configuration for Soft SaaS UI
- soft_saas_ui/typography
- Typography System for Soft SaaS UI
- soft_saas_ui/utils/color_utils
- Hex color parsing and normalization utilities.
- soft_saas_ui/utils/hex_color_formatter
- TextInputFormatter that enforces hex-color typing rules.