Plat logo
Plat

Composable pane layouts for Flutter.
Split panes, tab groups, drag-and-drop, and controller-driven workspaces.

pub package ci

About · Features · Getting started · Layout · Customization

About

Plat is a highly customizable and flexible package for building workspace layouts, from simple split panes to complex IDE-style editors. It provides tab groups, resizable splits, drag-and-drop, snapshots, undoable commands, content builders, and themeable chrome.

Features

  • Split workspaces: Compose rows, columns, slots, leaves, and tab groups.
  • Tab workflows: Reorder, drag, pin, lock, preview, close, and move tabs.
  • Drag-and-drop layouts: Move tabs or panes within one view or across views.
  • Resizable panes: Combine fixed, fractional, auto, minimum, and maximum extents.
  • Controller commands: Focus, close, insert, split, hide, maximize, undo, and redo.
  • Stable snapshots: Read layout state by id for rendering and commands.
  • Drop policies: Accept or reject drops by source controller, target, and zone.
  • Composable styling: Theme dividers, drop hints, tab bars, and tab chips.
  • Keyboard actions: Built-in shortcuts for focus and layout operations.

Getting started

Add plat to your app:

flutter pub add plat

Or add it manually:

dependencies:
  plat: ^0.1.1

Then import the package:

import 'package:plat/plat.dart';

Layout

A Plat tree describes the workspace shape:

  • Plat: a row, column, tab group, slot, or leaf.
  • Plat.row / Plat.column: split children horizontally or vertically.
  • Plat.tabs: group tabs and render the active tab's child.
  • PlatTab: tab metadata such as title, pinned, locked, and preview state.
  • Plat.leaf: a content endpoint rendered by your leafBuilder.
  • Plat.slot: a region for empty states, stable ids, and scoped maximize.
  • id: stable string identity for builders, focus, drops, and commands.
  • PlatSize / PlatExtent: fixed, fractional, auto, and resizable space.
final controller = PlatController(
  initialPlat: .row(
    children: [
      .tabs(
        [
          .leaf(id: 'main', title: 'main.dart'),
          .leaf(id: 'readme', title: 'README.md'),
        ],
        id: 'editors',
      ),
      const .slot(
        id: 'inspector',
        size: .fixed(.pixel(280)),
        child: .leaf(id: 'inspector-pane', title: 'Inspector'),
      ),
    ],
  ),
);

Rendering

PlatView renders the controller tree. Build each leaf with your widgets; Plat renders chrome, tabs, dividers, drops, shortcuts, and focus state. Switch on leaf.id, leaf.title, or leaf.data when panes need different content.

PlatView(
  controller: controller,
  leafBuilder: (context, leaf) => switch (leaf.id) {
    'inspector-pane' => InspectorPane(leaf: leaf),
    _ => EditorPane(leaf: leaf),
  },
);

Controller

Use PlatController to change the workspace. Structural changes support undo and redo.

controller.insertTab(
  tabGroupId: 'editors',
  tab: .leaf(id: 'settings', title: 'Settings'),
);

controller.split(
  targetId: 'main',
  side: .right,
  sibling: .tabs([
    .leaf(id: 'preview', title: 'Preview'),
  ]),
);

controller.close('readme');
controller.undo();

Customization

Theme

PlatTheme styles the layout chrome for the PlatViews below it. Use it for dividers, drop feedback, tabs, and animation timing.

PlatTheme(
  data: const PlatThemeData(
    divider: PlatDividerTheme(thickness: 2, hitSlop: 6),
    dropHint: PlatDropHintTheme(duration: Duration(milliseconds: 160)),
    tabBar: PlatTabBarTheme(
      size: 36,
      fit: .expand,
      chipMinWidth: 72,
      chipMaxWidth: 220,
      labelPadding: .symmetric(horizontal: 10),
    ),
  ),
  child: PlatView(
    controller: controller,
    leafBuilder: (context, leaf) => switch (leaf.id) {
      'inspector-pane' => InspectorPane(leaf: leaf),
      _ => EditorPane(leaf: leaf),
    },
  ),
);

Tab chrome

For a custom tab group, return a PlatTabBar from PlatView.tabBar. Reuse the default chip and replace the slots that need custom content.

PlatView(
  controller: controller,
  tabBar: (context, tabs) => PlatTabBar(
    tabBuilder: (context, tab) => PlatTabChip(
      leading: const Icon(Icons.description, size: 14),
      label: Text(tab.snapshot.title),
      trailing: const PlatTabCloseButton(),
    ),
  ),
  leafBuilder: (context, leaf) => switch (leaf.id) {
    'inspector-pane' => InspectorPane(leaf: leaf),
    _ => EditorPane(leaf: leaf),
  },
);

Multiple views

One PlatView can render deeply nested workspaces. Use multiple views when separate regions or controllers need to exchange tabs, filter drops, or preserve leaf state during handoff.

Widget buildPane(BuildContext context, LeafSnapshot leaf) {
  return switch (leaf.id) {
    'inspector-pane' => InspectorPane(leaf: leaf),
    _ => EditorPane(leaf: leaf),
  };
}

PlatScope(
  child: Row(
    children: [
      Expanded(
        child: PlatView(
          controller: mainController,
          leafBuilder: buildPane,
        ),
      ),
      Expanded(
        child: PlatView(
          controller: sideController,
          leafBuilder: buildPane,
          autofocus: false,
          dropPolicy: (attempt) => attempt.sourceController == mainController,
        ),
      ),
    ],
  ),
);

Libraries

plat
Resizable, reorderable workspace layouts for Flutter.