SkiaEditor
SkiaEditor is a fully drawn text editor rendered entirely on the SkiaSharp canvas. It uses a hidden native control (Android EditText, iOS UITextView, Windows TextBox) purely as a keyboard sink — all text rendering, cursor drawing, and selection happen in SkiaSharp. This gives pixel-perfect, fully styleable text input on every platform including Blazor WASM.
How it works
User tap → SkiaEditor gesture handler
↓ positions drawn cursor
↓ focuses hidden native control (1×1 px, invisible)
↓ platform IME opens
↓ keystrokes → native TextWatcher / delegate
↓ Text property updated
↓ SkiaLabel re-renders with new text
↓ cursor repositioned
The native control is never visible — it exists solely so the platform IME has a valid input target.
Basic usage
Single-line
<draw:SkiaEditor
HorizontalOptions="Fill"
MaxLines="1"
FontSize="16"
TextColor="Black"
CursorColor="DodgerBlue"
BackgroundColor="#F5F5F5"
Padding="12,8"
ReturnType="Done"
Text="{Binding Username}"
TextSubmitted="OnSubmit" />
Multiline
<draw:SkiaEditor
HorizontalOptions="Fill"
MaxLines="4"
FontSize="16"
TextColor="White"
CursorColor="White"
BackgroundColor="#1E1E2E"
Padding="12,8"
ReturnType="Send"
TextSubmitted="OnSend" />
<draw:SkiaEditor
MaxLines="1"
KeyboardType="Numeric"
ReturnType="Done"
Text="{Binding Amount}" />
Password
<draw:SkiaEditor
MaxLines="1"
IsPassword="True"
ReturnType="Done"
Text="{Binding Password}" />
Properties
Text
| Property |
Type |
Default |
Description |
Text |
string |
"" |
Current text value |
MaxLines |
int |
1 |
1 = single-line; >1 = multiline with that many visible lines |
IsMultiline |
bool |
— |
Read-only; true when MaxLines != 1 |
Appearance
| Property |
Type |
Default |
Description |
FontSize |
double |
14 |
Font size in logical pixels |
FontFamily |
string |
null |
Font family name |
FontWeight |
int |
400 |
Weight (400 normal, 700 bold) |
TextColor |
Color |
Black |
Drawn text color |
CursorColor |
Color |
Black |
Blinking cursor color |
SelectionColor |
Color |
— |
Selection highlight color |
LineHeight |
double |
1.3 |
Line height multiplier |
HorizontalTextAlignment |
DrawTextAlignment |
Start |
Text alignment |
Padding |
Thickness |
0 |
Inner padding around text |
UseMarkdown |
bool |
false |
Render text as Markdown |
| Property |
Type |
Default |
Description |
KeyboardType |
SkiaEditorKeyboard |
Default |
Software keyboard layout |
ReturnType |
ReturnType |
Done |
IME action button label and behavior |
IsPassword |
bool |
false |
Masks drawn text with •; disables autocorrect |
Focus and selection
| Property |
Type |
Default |
Description |
IsFocused |
bool |
false |
Set to true to open keyboard programmatically |
CursorPosition |
int |
0 |
Character index of the cursor |
SelectionLength |
int |
0 |
Number of selected characters |
KeyboardType
SkiaEditorKeyboard enum controls the software keyboard shown when the editor is focused.
| Value |
Android InputType |
iOS UIKeyboardType |
Description |
Default |
TYPE_CLASS_TEXT |
Default |
Standard QWERTY, autocorrect on |
Numeric |
TYPE_CLASS_NUMBER |
NumberPad |
Integers only |
Decimal |
TYPE_CLASS_NUMBER \| FLAG_DECIMAL |
DecimalPad |
Numbers with decimal point |
Phone |
TYPE_CLASS_PHONE |
PhonePad |
Phone number layout |
Email |
TYPE_TEXT_VARIATION_EMAIL_ADDRESS |
EmailAddress |
Email keyboard with @ and . keys |
ReturnType
Controls the label/action of the IME confirm button.
| Value |
Key label |
Behavior |
Done |
Done |
Closes keyboard, fires TextSubmitted |
Go |
Go |
Fires TextSubmitted |
Next |
Next |
Fires TextSubmitted |
Search |
Search |
Fires TextSubmitted |
Send |
Send |
Fires TextSubmitted |
On multiline editors the confirm key inserts a newline instead of submitting, regardless of ReturnType.
Events
| Event |
Signature |
Description |
TextChanged |
EventHandler<string> |
Fires on every keystroke |
FocusChanged |
EventHandler<bool> |
Fires when keyboard opens or closes |
TextSubmitted |
EventHandler<string> |
Fires when IME action button is tapped |
Commands
| Command |
Argument |
Description |
CommandOnSubmit |
string (current text) |
Executed on submit |
CommandOnFocusChanged |
bool (focus state) |
Executed on focus change |
CommandOnTextChanged |
string (current text) |
Executed on every keystroke |
Programmatic focus
// Open keyboard
myEditor.IsFocused = true;
// Close keyboard
myEditor.IsFocused = false;
// Move cursor to end
myEditor.CursorPosition = myEditor.Text?.Length ?? 0;
C# construction
var editor = new SkiaEditor
{
HorizontalOptions = LayoutOptions.Fill,
MaxLines = 1,
FontSize = 16,
TextColor = Colors.Black,
CursorColor = Colors.DodgerBlue,
BackgroundColor = Color.Parse("#F5F5F5"),
Padding = new Thickness(12, 8),
ReturnType = ReturnType.Done,
KeyboardType = SkiaEditor.SkiaEditorKeyboard.Default,
};
editor.TextSubmitted += (s, text) => Console.WriteLine(text);
<draw:SkiaEditor
MaxLines="4"
ReturnType="Send"
KeyboardType="Default"
FontSize="16"
TextColor="Black"
CursorColor="Black"
Padding="12,8"
TextSubmitted="OnSendMessage" />
MaxLines="4" caps visible height at 4 lines; the editor scrolls internally if the user types more. ReturnType="Send" shows the Send button on the IME.
Notes
- Cursor height adapts automatically to the rendered line height. Cursor width defaults to 2 px.
IsPassword masking (•) is drawn at the canvas layer. The hidden native control is always invisible, so OS-level password masking only affects Android's native TransformationMethod (irrelevant for display but prevents text leaking into autocomplete).
- On Blazor WASM the editor works without a native control — keyboard routing uses JS global key listeners.
- Embedding inside a
SkiaScroll works — the editor scroll and the outer scroll coexist via gesture routing.