Empty-content placeholders
Placeholders give readers a starting point when a document or block is empty. They are available by default: opening a new document shows “Start writing…”. Typing hides the hint immediately. Deleting all the text, or undoing back to an empty block, restores it. Whitespace and inline content count as content too.
Built-in defaults
Each block's editor extension owns its placeholder setting.
| Extension | Default hint | Gesture | When it appears |
|---|---|---|---|
ParagraphDocumentExtension | Start writing… | Writing pen | A single empty paragraph or heading in the document body |
ListDocumentExtension | Start writing… | Lines | A single empty list item in the document body |
CodeDocumentExtension | Write code… | Brackets | An empty code block |
ChecklistDocumentExtension | Add an item… | Lines | An empty checklist item |
BlockQuoteDocumentExtension | Write a quote… | Quotation marks | A quote with one empty text child |
Placeholders follow the resolved text style, baseline, alignment and zoom in paged and continuous layouts. They remain visible while the empty editor is focused, and are hidden in read-only modes.
Configure block owners
Import the block constructors from package:vyuh_document_editor/document_editor_extensions.dart. An extension's placeholder accepts one of these presentations:
| Setting | Result |
|---|---|
DocumentPlaceholder.animated('Write code…', gesture: DocumentPlaceholderGesture.brackets) | Native animated glyph, text reveal and trailing dots |
DocumentPlaceholder.text('Write code…') | Static text without a glyph |
DocumentPlaceholder.builder(builder, semanticLabel: 'Write code…') | A custom widget with a stable accessible label |
null | Disable that owner's hints |
Replace matching entries in the ordered extension list, preserving unrelated extensions and their order. This example customizes the first-writing prompt, sets a code prompt, and disables checklist hints:
dart
import 'package:vyuh_document_editor/document_editor.dart'
show DocumentExtension, DocumentPlaceholder, DocumentPlaceholderGesture;
import 'package:vyuh_document_editor/document_editor_extensions.dart'
show
ChecklistDocumentExtension,
CodeDocumentExtension,
ParagraphDocumentExtension;
const placeholderOwners = <DocumentExtension>[
ParagraphDocumentExtension(
placeholder: DocumentPlaceholder.text('Write your first idea…'),
),
CodeDocumentExtension(
placeholder: DocumentPlaceholder.animated(
'Write Dart code…',
gesture: DocumentPlaceholderGesture.brackets,
),
),
ChecklistDocumentExtension(placeholder: null),
];
final placeholderOwnersById = {
for (final owner in placeholderOwners) owner.definition.id: owner,
};
final placeholderExtensions = <DocumentExtension>[
for (final owner in DocumentExtension.defaults)
placeholderOwnersById[owner.definition.id] ?? owner,
];Pass the resulting list to the typed editor:
dart
DocumentEditor(
controller: controller,
extensions: placeholderExtensions,
surface: const DocumentSurface.pages(),
);Import this widget from package:vyuh_document_editor/document_editor.dart. Its DocumentController configuration still governs which content is allowed. The same extension list works with line, field and pageless surfaces.
For the host editor API, pass the list when creating its runtime controller:
dart
final controller = DocumentEditorController(
document: Document.empty(),
extensions: placeholderExtensions,
);Import that controller from package:vyuh_document_editor/vyuh_document_editor.dart. The runtime controller's extension set is fixed for its lifetime. Avoid appending a second registration for an owner already in the list. There is no document-wide placeholder override; changing the paragraph prompt does not change the code prompt.
Motion and accessibility
The built-in animation uses Flutter's AnimationController and a CustomPainter; it requires no Rive file or animation package. A 5.2-second loop traces the glyph, reveals the text, brings in three trailing dots, then holds a reading pause before restarting. The writing pen follows the reveal and returns to its starting position. All four gesture choices share this timeline:
DocumentPlaceholderGesture | Motif |
|---|---|
.writing | A moving pen |
.brackets | Code brackets |
.lines | Short list-like strokes |
.quotation | Quotation marks |
The text box stays the same size throughout the loop. Labels ending in … or ... use the animated dots; labels without that suffix keep their original text. The accessible label stays complete throughout the visual reveal.
Animation runs only while the hint is mounted and motion is enabled. Reduced motion or disabled tickers show the complete label and resting glyph. Typing removes the hint; clearing mounts it again. Choose DocumentPlaceholder.text when you want a static presentation regardless of motion preferences.
Custom widgets
Use a builder for a different illustration, icon or animation. The renderer provides the owner's resolved text style and bounds:
dart
CodeDocumentExtension(
placeholder: DocumentPlaceholder.builder(
(context, target) => Text(
'Your code goes here…',
style: target.style,
textDirection: target.textDirection,
textAlign: target.textAlign,
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
semanticLabel: 'Your code goes here…',
),
);Import Text and its supporting types from package:flutter/widgets.dart. DocumentPlaceholderContext supplies:
| Property | Use |
|---|---|
owner | Inspect the real immutable block that owns the empty content |
isDocument | Distinguish the sole body block from a block in a larger document |
bounds | Fit within the unscaled, occurrence-local space already resolved for the hint |
style | Match the block's resolved typography and hint color |
textDirection, textAlign | Respect the block's text direction and alignment |
animationsEnabled | Choose animated or complete static content |
Custom animations should respect target.animationsEnabled and dispose their own animation controllers. Keep the widget within target.bounds. The shared renderer clips it and leaves pointer and keyboard input with the real block; placeholder widgets are hints rather than interactive buttons.
Extension authors
A custom element declares its empty-content target through DocumentElementDefinition.emptyContent in its pure-Dart definition. The target identifies an existing text leaf or an empty frame. Its Flutter BlockDescriptor.placeholder supplies the presentation; placeholderVisibility chooses emptyBlock or emptyDocument.
The shared placeholder layer projects that target onto the current resolved layout, so owners reuse the existing input, selection and layout paths. No placeholder node is inserted into the document. Hints never enter saved JSON, clipboard payloads, undo history, PDF or other exports.
See Build an extension for the registration contracts.
Try the behavior
Open the empty document or the empty-block showcase. Watch the hint, type some text, then clear it: the hint should disappear on the first character and return after the last character is removed.
Compare the plain text, custom prompts and disabled hints presets. Enable reduced motion to see the complete static hints.