Blazorise CodeEditor component

Add a full-featured source editor to a Blazorise application, starting with two-way binding and growing into custom language tooling.

CodeEditor provides syntax highlighting, diagnostics, completion, formatting, keyboard commands, and strongly typed APIs for common editing scenarios.

The examples below are ordered from basic to advanced. Start with Value and Language; add the other APIs only when the application needs them.

Structure

  • <CodeEditor> is the main editor component. It binds the document value and exposes options, events, diagnostics, completion, formatting, and programmatic commands.

    • <Feedback> displays validation feedback when the editor is used inside a Validation component.

    • <CodeEditorCustomLanguage> optionally declares a custom language inside the editor.

      • <CodeEditorTokenizer> contains the syntax-highlighting rules for the custom language.

        • <CodeEditorToken> defines a regular-expression rule and the token name applied to matching text.

        • <CodeEditorTokenizerState> groups rules for a named tokenizer state, such as a quoted string or multiline comment.

Installation

NuGet

Install the CodeEditor extension package.
Install-Package Blazorise.CodeEditor

Service registration

Register CodeEditor after the main Blazorise services and UI provider. The extension serves and loads its editor assets automatically.
builder.Services
    .AddBlazorise()
    .AddBootstrap5Providers()
    .AddBlazoriseCodeEditor();

Imports

Add the extension namespace to the application's main _Imports.razor.
@using Blazorise.CodeEditor

Examples

Binding

Use @bind-Value for normal two-way binding and choose one of the identifiers from CodeEditorLanguage. Immediate sends user changes to .NET while typing.
Characters: 78
<CodeEditor @bind-Value="@sourceCode"
            Language="@CodeEditorLanguage.CSharp"
            Theme="@CodeEditorTheme.VisualStudioDark"
            Immediate
            Height="220.Px()" />

<Div Margin="Margin.Is3.FromTop">
    <Span TextWeight="TextWeight.SemiBold">Characters:</Span>
    <Code>@sourceCode.Length</Code>
</Div>
@code {
    private string sourceCode = """
public static string Greet( string name )
{
    return $"Hello, {name}!";
}
""";
}

Options

EditorOptions contains editor-specific settings. Ready, Focused, Blurred, and ContentChanged describe the editor lifecycle and user activity. Debouncing keeps frequent server-side value updates under control.
Waiting for the editor
<CodeEditor @ref="@editor"
            @bind-Value="@sourceCode"
            Language="@CodeEditorLanguage.JavaScript"
            EditorOptions="@editorOptions"
            Immediate
            Debounce
            DebounceInterval="250"
            Ready="@OnReady"
            ContentChanged="@OnContentChanged"
            Focused="@OnFocused"
            Blurred="@OnBlurred"
            Height="220.Px()" />

<Div Display="Display.Flex" Flex="Flex.AlignItems.Center" Gap="Gap.Is3" Margin="Margin.Is3.FromTop">
    <Button Color="Color.Primary" Clicked="@FocusEditor">Focus editor</Button>
    <Span>@status</Span>
</Div>
@code {
    private CodeEditor editor;

    private string status = "Waiting for the editor";

    private string sourceCode = """
const greeting = name => `Hello, ${name}!`;

console.log(greeting("Blazorise"));
""";

    private readonly CodeEditorOptions editorOptions = new()
    {
        Minimap = false,
        WordWrap = true,
        TabSize = 2,
        RenderWhitespace = true,
        ScrollBeyondLastLine = false,
        FontSize = 14,
    };

    private Task FocusEditor()
        => editor?.Focus() ?? Task.CompletedTask;

    private Task OnReady( CodeEditorReadyEventArgs eventArgs )
    {
        status = $"Ready: {eventArgs.ElementId}";

        return Task.CompletedTask;
    }

    private Task OnContentChanged( string value )
    {
        status = $"Changed: {value.Length} characters";

        return Task.CompletedTask;
    }

    private Task OnFocused()
    {
        status = "Focused";

        return Task.CompletedTask;
    }

    private Task OnBlurred()
    {
        status = "Blurred";

        return Task.CompletedTask;
    }
}

Diagnostics

Built-in language support can report syntax diagnostics for supported languages. GetDiagnostics returns a snapshot of the markers currently available, including markers supplied through the Diagnostics parameter.

Edit the JSON or add an application warning.

<CodeEditor @ref="@editor"
            @bind-Value="@json"
            Language="@CodeEditorLanguage.Json"
            Diagnostics="@applicationDiagnostics"
            Immediate
            Height="220.Px()" />

<Div Display="Display.Flex" Flex="Flex.Wrap.AlignItems.Center" Gap="Gap.Is2" Margin="Margin.Is3.FromTop">
    <Button Color="Color.Primary" Clicked="@ReadDiagnostics">Read diagnostics</Button>
    <Button Color="Color.Warning" Clicked="@AddApplicationWarning">Add application warning</Button>
    <Button Color="Color.Light" Clicked="@ClearApplicationDiagnostics">Clear application markers</Button>
</Div>

<Paragraph Margin="Margin.Is3.FromTop">
    @status
</Paragraph>
@code {
    private CodeEditor editor;

    private string status = "Edit the JSON or add an application warning.";

    private string json = """
{
  "name": "Blazorise",
  "debug": true
}
""";

    private IReadOnlyList<CodeEditorDiagnostic> applicationDiagnostics = [];

    private async Task ReadDiagnostics()
    {
        IReadOnlyList<CodeEditorDiagnostic> diagnostics = await editor.GetDiagnostics();
        CodeEditorDiagnostic firstError = diagnostics.FirstOrDefault( diagnostic => diagnostic.Severity == CodeEditorDiagnosticSeverity.Error );

        status = firstError is null
            ? $"No error markers are currently available. The editor reports {diagnostics.Count} marker(s)."
            : $"Line {firstError.StartLineNumber}: {firstError.Message}";
    }

    private Task AddApplicationWarning()
    {
        applicationDiagnostics =
        [
            new()
            {
                Severity = CodeEditorDiagnosticSeverity.Warning,
                Message = "Disable debug mode before publishing.",
                Code = "APP001",
                StartLineNumber = 3,
                StartColumn = 3,
                EndLineNumber = 3,
                EndColumn = 16,
            },
        ];
        status = "Application warning added.";

        return Task.CompletedTask;
    }

    private Task ClearApplicationDiagnostics()
    {
        applicationDiagnostics = [];
        status = "Application markers cleared.";

        return Task.CompletedTask;
    }
}

Validation

Put CodeEditor inside the standard Blazorise validation components. The validator runs only when requested and uses the currently available diagnostics to reject documents that contain error markers.
@using System.Threading

<Validations @ref="@validations" Mode="ValidationMode.Manual">
    <Validation AsyncValidator="@ValidateCode">
        <Field>
            <FieldLabel>JSON configuration</FieldLabel>
            <FieldBody>
                <CodeEditor @ref="@editor"
                            @bind-Value="@json"
                            Language="@CodeEditorLanguage.Json"
                            Immediate
                            Height="220.Px()">
                    <Feedback>
                        <ValidationError />
                    </Feedback>
                </CodeEditor>
            </FieldBody>
        </Field>
    </Validation>

    <Button Color="Color.Primary" Clicked="@Validate">Validate</Button>
</Validations>
@code {
    private CodeEditor editor;

    private Validations validations;

    private string json = """
{
  "name": "Blazorise",
  "enabled":
}
""";

    private Task Validate()
        => validations.ValidateAll();

    private async Task ValidateCode( ValidatorEventArgs eventArgs, CancellationToken cancellationToken )
    {
        cancellationToken.ThrowIfCancellationRequested();

        string value = Convert.ToString( eventArgs.Value );

        if ( string.IsNullOrWhiteSpace( value ) )
        {
            eventArgs.Status = ValidationStatus.Error;
            eventArgs.ErrorText = "Enter a JSON document.";

            return;
        }

        IReadOnlyList<CodeEditorDiagnostic> diagnostics = await editor.GetDiagnostics();

        cancellationToken.ThrowIfCancellationRequested();

        CodeEditorDiagnostic error = diagnostics.FirstOrDefault(
            diagnostic => diagnostic.Severity == CodeEditorDiagnosticSeverity.Error );

        eventArgs.Status = error is null
            ? ValidationStatus.Success
            : ValidationStatus.Error;
        eventArgs.ErrorText = error?.Message;
    }
}

Completion

Use CompletionItems when the same suggestions are valid everywhere. InsertAsSnippet enables numbered placeholders, and CompletionTriggerCharacters can open suggestions after characters such as a dot. Press Ctrl+Space to request suggestions manually.

Type console., or press Ctrl+Space, and select a suggestion. Press Tab to move through snippet placeholders.

<Paragraph>
    Type <Code>console.</Code>, or press <Code>Ctrl+Space</Code>, and select a suggestion. Press <Code>Tab</Code> to move through snippet placeholders.
</Paragraph>

<CodeEditor @bind-Value="@sourceCode"
            Language="@CodeEditorLanguage.JavaScript"
            CompletionItems="@completionItems"
            CompletionTriggerCharacters="@triggerCharacters"
            Immediate
            Height="220.Px()" />
@code {
    private string sourceCode = """
function greet( name ) {
    // Type console. here
}
""";

    private static readonly IReadOnlyList<string> triggerCharacters = ["."];

    private static readonly IReadOnlyList<CodeEditorCompletionItem> completionItems =
    [
        new()
        {
            Label = "log",
            InsertText = "log(${1:value});",
            Kind = CodeEditorCompletionItemKind.Method,
            Detail = "Write a message to the console",
            Documentation = "Inserts console.log with an editable value placeholder.",
            InsertTextRules = CodeEditorCompletionItemInsertTextRule.InsertAsSnippet,
        },
        new()
        {
            Label = "warn",
            InsertText = "warn(${1:value});",
            Kind = CodeEditorCompletionItemKind.Method,
            Detail = "Write a warning to the console",
            InsertTextRules = CodeEditorCompletionItemInsertTextRule.InsertAsSnippet,
        },
        new()
        {
            Label = "error",
            InsertText = "error(${1:value});",
            Kind = CodeEditorCompletionItemKind.Method,
            Detail = "Write an error to the console",
            InsertTextRules = CodeEditorCompletionItemInsertTextRule.InsertAsSnippet,
        },
    ];
}

Custom language

Supply Languages when editing a domain-specific language. Each CodeEditorLanguageDefinition registers an identifier, while its Tokenizer maps regular-expression rules to token names used for syntax highlighting.
<CodeEditor @bind-Value="@workflow"
            Language="@workflowLanguageId"
            Languages="@workflowLanguages"
            Theme="@CodeEditorTheme.VisualStudioDark"
            Immediate
            Height="220.Px()" />
@code {
    private const string workflowLanguageId = "sample-workflow";

    private string workflow = """
# A small domain-specific workflow
step Build
when success
run "dotnet build"
""";

    private static readonly IReadOnlyList<CodeEditorLanguageDefinition> workflowLanguages =
    [
        new()
        {
            Id = workflowLanguageId,
            Aliases = ["Workflow"],
            Extensions = [".workflow"],
            Tokenizer = new()
            {
                IgnoreCase = true,
                DefaultToken = string.Empty,
                Tokens =
                [
                    new() { Pattern = "\\s+", Token = "white" },
                    new() { Pattern = "#.*$", Token = "comment" },
                    new() { Pattern = "\"[^\"\\r\\n]*\"", Token = "string" },
                    new() { Pattern = "\\b(?:step|when|run|success|failure)\\b", Token = "keyword" },
                    new() { Pattern = "\\b\\d+\\b", Token = "number" },
                    new() { Pattern = "[A-Za-z_][A-Za-z0-9_-]*", Token = "identifier" },
                ],
            },
        },
    ];
}

Custom languages can also be composed declaratively with CodeEditorCustomLanguage, CodeEditorTokenizer, CodeEditorTokenizerState, and CodeEditorToken. Named tokenizer states are useful for strings, comments, and other constructs that need enter-and-exit rules. Language registrations are page-wide, so reuse one definition for each language identifier.

Dynamic completion

Use CodeEditorCompletionProvider.ItemsProvider when suggestions depend on the document or cursor. The callback receives the current value, line, position, word, and trigger character. An item's optional Range tells the editor exactly what text to replace.

Type an opening brace followed by part of a field name, for example {Customer.

<Paragraph>
    Type an opening brace followed by part of a field name, for example <Code>{Customer</Code>.
</Paragraph>

<CodeEditor @bind-Value="@template"
            Language="@CodeEditorLanguage.PlainText"
            CompletionProvider="@completionProvider"
            Immediate
            Height="220.Px()" />
@code {
    private string template = "Invoice for ";

    private static readonly IReadOnlyList<string> fields =
    [
        "Customer.Name",
        "Customer.Email",
        "Invoice.Number",
        "Invoice.Total",
    ];

    private static readonly CodeEditorCompletionProvider completionProvider = new()
    {
        Language = CodeEditorLanguage.PlainText,
        TriggerCharacters = ["{"],
        ItemsProvider = ProvideFields,
    };

    private static Task<IReadOnlyList<CodeEditorCompletionItem>> ProvideFields( CodeEditorCompletionContext context )
    {
        CodeEditorCompletionRange range = FindFieldRange( context );

        if ( range is null )
            return Task.FromResult<IReadOnlyList<CodeEditorCompletionItem>>( [] );

        IReadOnlyList<CodeEditorCompletionItem> items = fields
            .Select( field =>
            {
                string expression = $"{{{field}}}";

                return new CodeEditorCompletionItem
                {
                    Label = expression,
                    InsertText = expression,
                    FilterText = expression,
                    Kind = CodeEditorCompletionItemKind.Field,
                    Detail = "Template field",
                    Range = range,
                };
            } )
            .ToArray();

        return Task.FromResult( items );
    }

    private static CodeEditorCompletionRange FindFieldRange( CodeEditorCompletionContext context )
    {
        if ( context is null || context.LineNumber < 1 || context.Column < 1 )
            return null;

        string line = context.LineText ?? string.Empty;
        int cursorIndex = Math.Min( context.Column - 1, line.Length );
        string textBeforeCursor = line[..cursorIndex];
        int openingIndex = textBeforeCursor.LastIndexOf( '{' );
        int closingIndex = textBeforeCursor.LastIndexOf( '}' );

        if ( openingIndex <= closingIndex )
            return null;

        int endColumn = cursorIndex < line.Length && line[cursorIndex] == '}'
            ? context.Column + 1
            : context.Column;

        return new()
        {
            StartLineNumber = context.LineNumber,
            StartColumn = openingIndex + 1,
            EndLineNumber = context.LineNumber,
            EndColumn = endColumn,
        };
    }
}

Completion positions and ranges use one-based line and column values. Return an empty list when the current context should not offer suggestions. Prefer CompletionItems for fixed data because static suggestions stay entirely in the browser; use ItemsProvider only when .NET needs to inspect the context.

Formatting

A FormattingProvider connects the Format Document action to application code. Call FormatDocument from a button, or let users invoke the standard keyboard command.
Ready
@using System.Text.Json

<CodeEditor @ref="@editor"
            @bind-Value="@json"
            Language="@CodeEditorLanguage.Json"
            FormattingProvider="@formattingProvider"
            EditorOptions="@editorOptions"
            Immediate
            Height="220.Px()" />

<Div Display="Display.Flex" Flex="Flex.AlignItems.Center" Gap="Gap.Is3" Margin="Margin.Is3.FromTop">
    <Button Color="Color.Primary" Clicked="@FormatDocument">Format document</Button>
    <Span>@status</Span>
</Div>
@code {
    private CodeEditor editor;

    private string status = "Ready";

    private string json = """{"name":"Blazorise","features":["completion","diagnostics","formatting"]}""";

    private static readonly JsonSerializerOptions serializerOptions = new()
    {
        WriteIndented = true,
    };

    private static readonly CodeEditorOptions editorOptions = new()
    {
        Minimap = false,
        FormatOnPaste = true,
        ScrollBeyondLastLine = false,
    };

    private static readonly CodeEditorDocumentFormattingProvider formattingProvider = new()
    {
        Language = CodeEditorLanguage.Json,
        Formatter = FormatJson,
    };

    private async Task FormatDocument()
    {
        bool formatted = await editor.FormatDocument();

        status = formatted
            ? "Formatting provider invoked."
            : "No formatting provider is available.";
    }

    private static Task<string> FormatJson( string value )
    {
        try
        {
            using JsonDocument document = JsonDocument.Parse( value ?? string.Empty );
            string formattedValue = JsonSerializer.Serialize( document.RootElement, serializerOptions );

            return Task.FromResult( formattedValue );
        }
        catch ( JsonException )
        {
            return Task.FromResult( value );
        }
    }
}

Keyboard commands

Common commands include Ctrl+Space for completion, Ctrl+F or Cmd+F for find, and Shift+Alt+F or Shift+Option+F for Format Document when a formatting provider is available.

Programmatic control

Keep an @ref when the application needs commands that are not naturally expressed through parameters. Common methods include Focus, GetValue, SetValue, GetSelection, SetSelection, RevealLine, Resize, GetDiagnostics, and FormatDocument. Update language, theme, diagnostics, completion, formatting, and custom language configuration through component parameters.

API

Parameters

Parameter Description TypeDefault
AriaDescribedBy

Specifies the aria-describedby attribute value.

Remarks

When set, this value is rendered as-is and overrides help and validation message ids generated by Field and Validation.

string
AriaInvalid

Specifies the aria-invalid attribute value.

Remarks

When set, this value is rendered as-is and overrides the validation-derived aria-invalid state.

string
AriaLabelledBy

Specifies the aria-labelledby attribute value.

Remarks

When set, this value is rendered as-is. Some non-labelable controls can otherwise derive it automatically from a parent FieldLabel or FieldsLabel.

string
AriaRequired

Specifies the aria-required attribute value.

Remarks

When set, this value overrides the required-field state resolved from the parent Validation.

boolfalse
Autofocus

Set's the focus to the component after the rendering is done.

boolfalse
ChildContent

Specifies the content to be rendered inside this CodeEditor.

RenderFragmentnull
CompletionItems

Gets or sets completion items.

IReadOnlyList<CodeEditorCompletionItem>null
CompletionProvider

Gets or sets the completion provider.

CodeEditorCompletionProvidernull
CompletionTriggerCharacters

Gets or sets the characters that trigger completion.

IReadOnlyList<string>null
Debounce

Gets or sets whether user-originated value updates are debounced before being sent to .NET.

Remarks

When supplied, this value overrides the global Blazorise debounce option.

boolfalse
DebounceInterval

Gets or sets the debounce interval in milliseconds.

Remarks

When set, this value overrides the global Blazorise debounce interval.

int?null
Diagnostics

Gets or sets diagnostic markers.

IReadOnlyList<CodeEditorDiagnostic>null
Disabled

Add the disabled boolean attribute on an input to prevent user interactions and make it appear lighter.

boolfalse
EditorOptions

Gets or sets additional editor options.

CodeEditorOptionsnull
Feedback

Placeholder for validation messages.

RenderFragmentnull
FormattingProvider

Gets or sets the document formatting provider.

CodeEditorDocumentFormattingProvidernull
Immediate

Gets or sets whether user-originated value updates are sent to .NET while typing.

Remarks

When supplied, this value overrides the global Blazorise immediate option. When disabled, the value is sent on blur.

boolfalse
Language

Gets or sets the editor language.

string
Languages

Gets or sets custom language definitions.

Remarks

Custom language registrations are global to the page. Use one definition per language identifier.

IReadOnlyList<CodeEditorLanguageDefinition>null
ReadOnly

Add the readonly boolean attribute on an input to prevent modification of the input’s value.

boolfalse
Size

Sets the size of the input control.

Size?null
TabIndex

If defined, indicates that its element can be focused and can participates in sequential keyboard navigation.

int?null
Theme

Gets or sets the editor theme.

Remarks

Themes are global. Changing the theme updates every code editor on the page.

string
Value

Specifies the value inside the input field.

TValuenull
ValueExpression

Specifies an expression that identifies the input value.

Expression<Func<TValue>>null

Events

Event Description Type
Blur

The blur event fires when an element has lost focus.

EventCallback<FocusEventArgs>
Blurred

Notifies when the editor loses focus.

EventCallback
ContentChanged

Notifies when user-originated editor content changes.

Remarks

Programmatic value updates do not raise this event.

EventCallback<string>
CustomValidationValue

Used to provide custom validation value on which the validation will be processed with the Validator handler.

Remarks

Should be used carefully as it's only meant for some special cases when input is used in a wrapper component, like Autocomplete or SelectList.

Func<TValue>
Focused

Notifies when the editor gains focus.

EventCallback
FocusIn

Notifies when the input box gains focus.

EventCallback<FocusEventArgs>
FocusOut

Notifies when the input box loses focus.

EventCallback<FocusEventArgs>
KeyDown

Notifies when a key is pressed down while the control has focus.

EventCallback<KeyboardEventArgs>
KeyPress

Notifies when a key is pressed while the control has focus.

EventCallback<KeyboardEventArgs>
KeyUp

Notifies when a key is released while the control has focus.

EventCallback<KeyboardEventArgs>
OnFocus

Notifies when the input box gains or loses focus.

EventCallback<FocusEventArgs>
Ready

Notifies when the editor is initialized.

EventCallback<CodeEditorReadyEventArgs>
ValueChanged

Occurs after value has changed.

EventCallback<TValue>

Methods

Method DescriptionReturnParameters
FormatDocument Formats the current document. Task<bool>
GetDiagnostics Gets the diagnostic markers for the current editor model.
Remarks

Returns a snapshot of the markers currently available. This method does not start or wait for asynchronous language analysis to complete.

Task<IReadOnlyList<CodeEditorDiagnostic>>
GetSelection Gets the current editor selection. Task<CodeEditorSelection>
GetValue Gets the current editor value. Task<string>
Resize Recalculates the editor dimensions to fit its container. Task
RevealLine Reveals the specified line. Taskint lineNumber
SetSelection Sets the current editor selection. TaskCodeEditorSelection selection
SetValue Sets the current editor value. Taskstring value
Focus Sets the focus on the underline element. Taskbool scrollToElement
Revalidate Forces the Validation (if any is used) to re-validate with the new custom or internal value. Task
On this page