Document Editor
Two controls, on both hosts:
| Control | What it is |
|---|---|
DocumentEditor |
the lone editing surface — canvas, caret, selection, typing. No chrome. |
DocumentEditorView |
DocumentEditor plus a formatting toolbar |
Same packages as the viewers (Shiny.Maui.Controls.Office / Shiny.Blazor.Controls.Office), same two
constraints: MAUI needs UseSkiaSharp(), Blazor is WASM-only, and on Blazor the container needs
an explicit height.
Open a document for editing
Section titled “Open a document for editing”editable: true is required — a read-only document throws on any edit.
using var document = await WordDocument.OpenAsync("report.docx", editable: true);Blazor
Section titled “Blazor”<div style="height:520px"> <DocumentEditorView Document="document" DocumentChanged="OnChanged" /></div>
@* or the bare surface, with your own chrome: *@<div style="height:520px"> <DocumentEditor @ref="editor" Document="document" /></div><office:DocumentEditorView x:Name="Editor" Document="{Binding Document}" /><office:DocumentEditor x:Name="BareEditor" Document="{Binding Document}" />The toolbar is composed from what each host has
Section titled “The toolbar is composed from what each host has”This is deliberate and asymmetric, because the two hosts own different halves of the chrome:
- MAUI has
FontPickerButton/FontSizePickerButtonbut no toolbar — soDocumentEditorViewbuilds a scrolling row of MAUI primitives and drops those two pickers into it. - Blazor has
ShinyToolbarbut no font picker — so it composesShinyToolbarand uses plain<select>elements for family and size.
The API and behaviour match on both; only the internals differ. Do not emit shiny:ShinyToolbar in
XAML, and do not expect a FontPicker component in Blazor.
Driving it
Section titled “Driving it”Everything lives on the shared controller, identical on both hosts:
var c = editor.Controller; // DocumentEditorController
c.InsertText("hello");c.InsertParagraph(); // Enterc.DeleteBackward(); // Backspacec.Move(CaretMove.WordRight, extend: true);c.SelectAll();
c.ToggleBold(); c.ToggleItalic(); c.ToggleUnderline(); c.ToggleStrikethrough();c.SetFontFamily("Cambria");c.SetFontSize(14); // pointsc.SetTextColor(new ArgbColor(255, 0xC0, 0, 0));c.SetAlignment(TextAlignment.Center);
c.Undo(); c.Redo();c.CaretFormat; // what a toolbar should show as activec.Selection.Range;Saving is the same as everywhere else — and an unedited document still saves byte-identical:
await document.SaveAsAsync("edited.docx");Keyboard input
Section titled “Keyboard input”Blazor: complete. Typing goes through beforeinput, so IME composition, autocorrect, dictation and
paste all work. Arrows, Home/End, Ctrl/Cmd+B/I/U, Ctrl/Cmd+Z and Shift+Ctrl/Cmd+Z are wired.
MAUI: typing works — a hidden Entry gives the platform keyboard and IME somewhere to send text.
Physical keys do not, because MAUI exposes no portable key-down event. Route them yourself:
Editor.HandleKey(EditorKey.Left, shift: true);Editor.HandleKey(EditorKey.Undo, control: true);A desktop host adds its own platform hook (NSEvent on macOS, KeyDown on Windows) and calls that.
Tapping, selection, typing and every toolbar command work without it.
Spell check
Section titled “Spell check”On by default, and on MAUI the checker is the platform’s own:
| Platform | Checker |
|---|---|
| iOS, Mac Catalyst | UITextChecker |
| macOS (AppKit) | NSSpellChecker |
| Android | SpellCheckerSession via text services |
| Windows | ISpellChecker (COM) |
| Blazor / plain .NET | none — supply one |
Nothing has to be registered: referencing Shiny.Maui.Controls.Office installs it. Using the
platform’s checker rather than shipping a dictionary is the point — it is the user’s dictionary, so
words they have already taught their keyboard are known, and Add to dictionary writes back to it
and is shared with every other app on the device.
Misspellings get a red wavy underline. Right-click, or long-press on touch, for the corrections along with Ignore and Add to dictionary. Applying a correction is a single undo step.
Blazor has to be given one
Section titled “Blazor has to be given one”The browser spell-checks its own editable elements and exposes neither the results nor the suggestions to script — and a canvas is not an editable element in the first place. So there is nothing to call, and Blazor defaults to no checking:
<DocumentEditorView Document="document" SpellChecker="myChecker" SpellCheckEnabled="true" />Supplying your own
Section titled “Supplying your own”Derive from SpellCheckerBase — it already handles the ignore list and language defaulting, leaving
two methods:
public sealed class MyChecker : SpellCheckerBase{ public override bool IsAvailable => true;
protected override ValueTask<IReadOnlyList<SpellingError>> CheckCoreAsync( string text, string language, CancellationToken cancellationToken) => ...;
protected override ValueTask<IReadOnlyList<string>> SuggestCoreAsync( string word, string language, CancellationToken cancellationToken) => ...;}Then per control (SpellChecker), or globally, before the first editor is constructed:
SpellCheckers.Default = new MyChecker();Registration uses SetDefaultIfUnset, so an explicit choice always wins and the platform checker is
never even constructed.
SpellingTokenizer is public and worth reusing: it skips acronyms, camelCase, numbers, URLs, email
addresses and paths — the things every dictionary flags and no reader wants underlined.
- Checking is per paragraph, cached on the paragraph’s text, and limited to the paragraphs on screen. Scrolling re-checks nothing already seen; editing re-checks one paragraph.
- Calls are debounced by 500 ms. A platform checker is interop — a service round trip on Android — and a half-typed word is not a mistake.
- ⚠️
IsAvailableis false when there is no checker or no dictionary for the language. Check it before telling a user spelling is on. - Set
SpellCheckEnabled/IsSpellCheckEnabledtofalseto turn it off entirely.
Not implemented
Section titled “Not implemented”- Formatting with an empty selection changes only
CaretFormat, not the document. Word applies it to whatever is typed next; that needs a pending-format concept the editor does not have yet. - Editing tables, images, lists (their text edits fine; structure does not).
- Cut/copy/paste through the clipboard, find and replace.
- Grammar checking. Android reports grammar errors and they are deliberately ignored, so all four platforms behave the same.
- Inserting new paragraph styles, images or tables.
- Everything the viewer does not render is still not rendered — see
document-viewer.md.
Screenshots
Section titled “Screenshots”| MAUI | Blazor |
|---|---|
![]() |
![]() |
The MAUI shot is running the platform spell checker — UITextChecker on iOS, registered with no
setup — which is why the misspellings are underlined.




