Skip to main content

Overview

Divider blocks create horizontal lines to visually separate sections of content. They are non-editable blocks that provide clear visual breaks in documents.

Block Keys

class DividerBlockKeys {
  static const String type = 'divider';
}
Divider blocks do not use delta or text content - they are purely visual elements. Source: /lib/src/editor/block_component/divider_block_component/divider_block_component.dart:5

Creating Divider Nodes

Use the dividerNode() helper function:
// Create a divider
final divider = dividerNode();
Dividers have no attributes or children by default. Source: /lib/src/editor/block_component/divider_block_component/divider_block_component.dart:12

Component Builder

Create a divider component with custom styling:
final dividerBuilder = DividerBlockComponentBuilder(
  configuration: BlockComponentConfiguration(
    padding: (node) => const EdgeInsets.symmetric(vertical: 8.0),
  ),
  lineColor: Colors.grey,
  height: 10,
  wrapper: (context, node, child) {
    // Optional wrapper for custom container
    return Container(
      margin: const EdgeInsets.symmetric(horizontal: 16.0),
      child: child,
    );
  },
);
Configuration Options:
  • lineColor - Color of the divider line (default: Colors.grey)
  • height - Total height of the divider block (default: 10)
  • wrapper - Optional widget wrapper for custom styling
Source: /lib/src/editor/block_component/divider_block_component/divider_block_component.dart:24

Widget Structure

class DividerBlockComponentWidget extends BlockComponentStatefulWidget {
  const DividerBlockComponentWidget({
    super.key,
    required super.node,
    super.showActions,
    super.actionBuilder,
    super.actionTrailingBuilder,
    super.configuration = const BlockComponentConfiguration(),
    this.lineColor = Colors.grey,
    this.height = 10,
    this.wrapper,
  });

  final Color lineColor;
  final double height;
  final DividerBlockWrapper? wrapper;
}
Source: /lib/src/editor/block_component/divider_block_component/divider_block_component.dart:63

Rendering

The divider is rendered as a Flutter Divider widget:
Widget child = Container(
  height: widget.height,
  alignment: Alignment.center,
  child: Divider(
    color: widget.lineColor,
    thickness: 1,
  ),
);

child = Padding(
  key: dividerKey,
  padding: padding,
  child: child,
);
Default Styling:
  • Height: 10px
  • Line thickness: 1px
  • Color: Grey
  • Alignment: Center
Source: /lib/src/editor/block_component/divider_block_component/divider_block_component.dart:100

Selection Behavior

Dividers support block selection:
child = BlockSelectionContainer(
  node: node,
  delegate: this,
  listenable: editorState.selectionNotifier,
  remoteSelection: editorState.remoteSelections,
  blockColor: editorState.editorStyle.selectionColor,
  cursorColor: editorState.editorStyle.cursorColor,
  selectionColor: editorState.editorStyle.selectionColor,
  supportTypes: const [
    BlockSelectionType.block,
    BlockSelectionType.cursor,
    BlockSelectionType.selection,
  ],
  child: child,
);
Dividers can be:
  • Selected as blocks - Click to select entire divider
  • Cursor positioned - Navigate to divider with arrow keys
  • Multi-selected - Include in range selections
Source: /lib/src/editor/block_component/divider_block_component/divider_block_component.dart:117

Cursor Style

Dividers use a special cursor style:
@override
bool get shouldCursorBlink => false;

@override
CursorStyle get cursorStyle => CursorStyle.cover;
The cursor covers the entire divider block without blinking. Source: /lib/src/editor/block_component/divider_block_component/divider_block_component.dart:159

Position Handling

Dividers have start and end positions:
@override
Position start() => Position(path: widget.node.path, offset: 0);

@override
Position end() => Position(path: widget.node.path, offset: 1);

@override
Position getPositionInOffset(Offset start) => end();
Source: /lib/src/editor/block_component/divider_block_component/divider_block_component.dart:150

Validation

Dividers must not have children:
@override
BlockComponentValidate get validate => (node) => node.children.isEmpty;
Source: /lib/src/editor/block_component/divider_block_component/divider_block_component.dart:60

Markdown Shortcuts

Create dividers using markdown syntax:
---
***
___
Type three or more hyphens, asterisks, or underscores on a line:
// Enable in editor:
characterShortcutEvents: [
  convertMinusesToDivider,    // ---
  convertStarsToDivider,      // ***
  convertUnderscoreToDivider, // ___
  // ... other shortcuts
],

Using in Editor

Add divider support to your editor:
AppFlowyEditor(
  editorState: editorState,
  blockComponentBuilders: {
    DividerBlockKeys.type: DividerBlockComponentBuilder(
      configuration: standardBlockComponentConfiguration.copyWith(
        padding: (node) => const EdgeInsets.symmetric(vertical: 8.0),
      ),
      lineColor: Colors.grey[300]!,
      height: 12,
    ),
    // ... other block types
  },
  characterShortcutEvents: [
    convertMinusesToDivider,
    convertStarsToDivider,
    convertUnderscoreToDivider,
    // ... other shortcuts
  ],
);

Inserting Dividers

// Insert a divider
final transaction = editorState.transaction;
transaction.insertNode(
  path,
  dividerNode(),
);
editorState.apply(transaction);

// Insert after current selection
final selection = editorState.selection;
if (selection != null) {
  final transaction = editorState.transaction;
  transaction.insertNode(
    selection.end.path.next,
    dividerNode(),
  );
  editorState.apply(transaction);
}

Custom Divider Styles

Dashed Line

DividerBlockComponentBuilder(
  wrapper: (context, node, child) {
    return Container(
      height: 10,
      alignment: Alignment.center,
      child: CustomPaint(
        size: const Size(double.infinity, 1),
        painter: DashedLinePainter(
          color: Colors.grey,
          dashWidth: 5,
          dashSpace: 3,
        ),
      ),
    );
  },
)

class DashedLinePainter extends CustomPainter {
  final Color color;
  final double dashWidth;
  final double dashSpace;

  DashedLinePainter({
    required this.color,
    required this.dashWidth,
    required this.dashSpace,
  });

  @override
  void paint(Canvas canvas, Size size) {
    final paint = Paint()
      ..color = color
      ..strokeWidth = 1;

    double startX = 0;
    while (startX < size.width) {
      canvas.drawLine(
        Offset(startX, 0),
        Offset(startX + dashWidth, 0),
        paint,
      );
      startX += dashWidth + dashSpace;
    }
  }

  @override
  bool shouldRepaint(CustomPainter oldDelegate) => false;
}

Gradient Divider

DividerBlockComponentBuilder(
  wrapper: (context, node, child) {
    return Container(
      height: 10,
      alignment: Alignment.center,
      child: Container(
        height: 2,
        decoration: const BoxDecoration(
          gradient: LinearGradient(
            colors: [
              Colors.transparent,
              Colors.blue,
              Colors.transparent,
            ],
          ),
        ),
      ),
    );
  },
)

Decorative Divider

DividerBlockComponentBuilder(
  wrapper: (context, node, child) {
    return Container(
      height: 20,
      alignment: Alignment.center,
      child: Row(
        children: [
          const Expanded(
            child: Divider(color: Colors.grey, thickness: 1),
          ),
          Padding(
            padding: const EdgeInsets.symmetric(horizontal: 8.0),
            child: Icon(
              Icons.circle,
              size: 6,
              color: Colors.grey,
            ),
          ),
          const Expanded(
            child: Divider(color: Colors.grey, thickness: 1),
          ),
        ],
      ),
    );
  },
)

Thick Divider

DividerBlockComponentBuilder(
  lineColor: Colors.grey[800]!,
  height: 16,
  wrapper: (context, node, child) {
    return Container(
      margin: const EdgeInsets.symmetric(vertical: 16.0),
      child: Divider(
        color: Colors.grey[800],
        thickness: 3,
      ),
    );
  },
)

Colored Section Divider

DividerBlockComponentBuilder(
  wrapper: (context, node, child) {
    // Get color from node attributes
    final colorValue = node.attributes['dividerColor'] as String?;
    final color = colorValue?.tryToColor() ?? Colors.grey;
    
    return Container(
      height: 12,
      alignment: Alignment.center,
      child: Divider(
        color: color,
        thickness: 2,
      ),
    );
  },
)

// Create colored divider:
final coloredDivider = Node(
  type: DividerBlockKeys.type,
  attributes: {
    'dividerColor': '0xFF2196F3', // Blue
  },
);

Slash Menu Integration

Add divider to slash menu:
final dividerMenuItem = SelectionMenuItem(
  getName: () => 'Divider',
  icon: (editorState, isSelected, style) => SelectionMenuIconWidget(
    icon: Icons.horizontal_rule,
    isSelected: isSelected,
    style: style,
  ),
  keywords: ['divider', 'separator', 'line', 'hr'],
  handler: (editorState, _, __) {
    final selection = editorState.selection;
    if (selection == null || !selection.isCollapsed) return;

    final node = editorState.getNodeAtPath(selection.start.path);
    if (node == null) return;

    final transaction = editorState.transaction;
    final delta = node.delta;
    
    if (delta != null && delta.isEmpty) {
      // Replace empty block with divider
      transaction
        ..insertNode(selection.start.path, dividerNode())
        ..deleteNode(node)
        ..afterSelection = Selection.collapsed(
          Position(path: selection.start.path.next, offset: 0),
        );
    } else {
      // Insert divider after current block
      transaction
        ..insertNode(selection.start.path.next, dividerNode())
        ..afterSelection = Selection.collapsed(
          Position(path: selection.start.path.next.next, offset: 0),
        );
    }
    
    editorState.apply(transaction);
  },
);
Navigating to/from dividers:
// Move to next block after divider
final dividerPath = [0, 1];
final nextPath = [...dividerPath];
nextPath[nextPath.length - 1]++;

final selection = Selection.collapsed(
  Position(path: nextPath, offset: 0),
);
editorState.updateSelection(selection);

Deletion

// Delete divider
final node = editorState.getNodeAtPath(path);
if (node?.type == DividerBlockKeys.type) {
  final transaction = editorState.transaction;
  transaction.deleteNode(node!);
  editorState.apply(transaction);
}

// Backspace on divider converts to paragraph
// This is handled by convertToParagraphCommand

Key Features

  • Visual Separation - Clear content boundaries
  • Non-Editable - No text content or children
  • Selectable - Can be selected and deleted
  • Keyboard Navigation - Arrow keys work properly
  • Markdown Support - ---, ***, ___ shortcuts
  • Customizable - Color, height, and wrapper styling
  • Block Selection - Full selection support

Use Cases

  1. Section Breaks - Separate major sections
  2. Visual Rhythm - Break up long content
  3. Thematic Shifts - Mark topic changes
  4. Before/After - Separate contrasting content
  5. List Separation - Divide related list groups

Best Practices

  1. Use sparingly - Too many dividers create visual clutter
  2. Consistent styling - Maintain uniform appearance
  3. Semantic meaning - Use for meaningful breaks
  4. Adequate spacing - Add vertical padding for breathing room
  5. Alternative options - Consider headings or whitespace instead

Accessibility

  • Dividers are keyboard navigable
  • Screen readers can identify dividers as separators
  • Selection and deletion work as expected
  • Proper ARIA roles for semantic HTML

Heading Blocks

Structure content with headings

Paragraph Blocks

Basic text blocks

Build docs developers (and LLMs) love