Opening Clicked Hyperlinks in the OS Default Browser

The problem in 2026

The SpiceLogic HTML Editor renders your document inside an embedded browser host. By default, a hyperlink click is handled inside that host - the page navigates within the editor in preview mode, or does nothing while the user is authoring. Customers consistently want the opposite: the click should launch the URL in the operating system's default browser - Microsoft Edge, Google Chrome, Brave, Firefox, whatever the user has set as the Windows default.

Historically this was framed as a "Microsoft Internet Explorer" problem. That framing is now obsolete. The modern editor is Chromium-backed, and customers expect their chosen browser to open - not whatever browser the editor itself hosts.

Design mode vs. Preview mode

The editor has three modes - EditorModes.WysiwygDesign, EditorModes.HtmlEdit, and EditorModes.ReadOnlyPreview. Hyperlink clicks behave differently in each: in WysiwygDesign a click places the caret inside the link (no navigation), in HtmlEdit the user is editing raw text so no DOM click happens, and in ReadOnlyPreview the user is previewing the final document - this is where you almost always want a click to launch the OS default browser. The HtmlElementClicked event fires in all three modes; gate the launch behaviour on htmlEditor1.EditorMode == EditorModes.ReadOnlyPreview so the user is never pulled out of the editor mid-edit.

The hook: HtmlElementClicked + FireHtmlElementClickEventFor

The editor exposes a CLR event HtmlElementClicked on WinFormHtmlEditor whose args (SpiceLogic.HtmlEditor.WinForms.Models.BOs.EditorEventArgs.HtmlElementClickedEventArgs) carry the clicked DOM element as ClickedElement (a System.Windows.Forms.HtmlElement), the underlying HtmlElementEventArgs as EventData, and a strongly-typed ElementType of enum HtmlElementTypes (Hyperlink, Image, Table, etc.). To make the event fire only for hyperlink clicks and skip the noise from spans, divs, and paragraphs, set htmlEditor1.Options.FireHtmlElementClickEventFor = HtmlElementTypes.Hyperlink;.

Inside the handler, cancel the default in-host navigation and launch the URL via System.Diagnostics.Process.Start with UseShellExecute = true. UseShellExecute = true is the part that matters - it tells Windows to resolve the URL through the registered protocol handler for http / https, which is the user's OS default browser.

Full WinForms snippet

using System;
using System.Diagnostics;
using SpiceLogic.HtmlEditor.Abstractions;
using SpiceLogic.HtmlEditor.WinForms.Models.BOs.EditorEventArgs;

// In your form Load handler:
htmlEditor1.Options.FireHtmlElementClickEventFor = HtmlElementTypes.Hyperlink;
htmlEditor1.HtmlElementClicked += HtmlEditor1_HyperlinkClicked;

private void HtmlEditor1_HyperlinkClicked(object sender, HtmlElementClickedEventArgs e)
{
    // Only launch in Preview mode - never while the user edits.
    if (htmlEditor1.EditorMode != EditorModes.ReadOnlyPreview) return;
    if (e.ElementType != HtmlElementTypes.Hyperlink || e.ClickedElement == null) return;

    string href = e.ClickedElement.GetAttribute("href");
    if (!TryGetSafeExternalUrl(href, out string safeUrl)) return;

    if (e.EventData != null) e.EventData.ReturnValue = false; // cancel in-host nav

    Process.Start(new ProcessStartInfo
    {
        FileName        = safeUrl,
        UseShellExecute = true   // routes through the OS default browser
    });
}

private static bool TryGetSafeExternalUrl(string href, out string url)
{
    url = null;
    if (string.IsNullOrWhiteSpace(href)) return false;
    if (href.StartsWith("#")) return false;                  // in-page anchor
    if (href.StartsWith("javascript:", StringComparison.OrdinalIgnoreCase))
        return false;                                        // script injection
    if (!Uri.TryCreate(href, UriKind.Absolute, out Uri parsed)) return false;

    if (parsed.Scheme == Uri.UriSchemeHttp
     || parsed.Scheme == Uri.UriSchemeHttps
     || parsed.Scheme == Uri.UriSchemeMailto
     || parsed.Scheme == "tel")
    {
        url = parsed.AbsoluteUri;
        return true;
    }
    return false;
}

WPF variant (same idea, different event-args)

If you also ship the WPF control, the same pattern applies on WpfHtmlEditor. The differences: the event args type is SpiceLogic.HtmlEditor.WPF.EditorEventArgs.HtmlElementClickedEventArgs (a RoutedEventArgs), ClickedElement is an mshtml.IHTMLElement so you read the href via e.ClickedElement.getAttribute("href", 0) as string, and to cancel the default navigation you set e.Handled = true; instead of e.EventData.ReturnValue = false;. The TryGetSafeExternalUrl helper is reusable verbatim - it has no UI dependencies.

Edge cases worth handling

  • Anchor jumps inside the same document (<a href="#footnote-3">) - do not launch the browser; let the host scroll. The helper filters these out by checking for a leading #.
  • javascript: pseudo-URLs - never pass these to Process.Start. They are a script-execution vector, not a navigation request.
  • mailto: and tel: - safe to shell-execute. Windows routes them through the user's default mail client / phone-dialer association.
  • file:// URLs - the helper rejects these by default; whitelist Uri.UriSchemeFile if you want them, but treat any file: href from untrusted HTML with suspicion.
  • Relative URLs (about-us.html) - rejected by UriKind.Absolute; combine with htmlEditor1.BaseUrl before launching if you need them resolved.
  • Null or empty href - the helper short-circuits on whitespace.

Security note

If the HTML your editor displays comes from untrusted input, validate the URL before calling Process.Start. The whitelist approach in TryGetSafeExternalUrl - check the parsed Uri.Scheme against an explicit allow-list of http, https, mailto, tel - is the correct pattern. Never pass a raw href from the DOM straight to Process.Start with UseShellExecute = true: a malicious document can specify shell:, vbscript:, or a custom protocol handler that runs arbitrary code on the user's machine.

What about the older "Hyperlink in default browser instead of MSIE" page?

The legacy WinForms Programming-category page describes the same idea using Internet Explorer era terminology. Prefer this page for any new code - the HtmlElementClicked + Options.FireHtmlElementClickEventFor + UseShellExecute = true pattern works against the modern Chromium-backed editor and routes through whichever browser the user has set as their Windows default.

Last updated on May 14, 2026