Add a custom button to the built-in toolbar

Sarah runs the internal-tools squad at a fast-growing HR-tech startup. Her recruiting team lives inside a Windows desktop app she built last year, and last Monday the head of recruiting cornered her in the kitchen: "Every offer letter we send out has the same six paragraphs of legal boilerplate. The recruiters are still pasting it in by hand from a Google Doc, and last week somebody shipped a letter without the equity disclaimer. Can the editor just have an 'Insert Offer Letter Template' button?"

Sarah's app already uses WinFormHtmlEditor for the offer-letter compose screen. She does not want to rip out the toolbars - the recruiters use Bold, Italic, lists, and the image button to drop in the company logo. She just wants one extra button on the same row, with the company's document icon, that drops a known block of HTML at the cursor.

Sarah opens the designer

She selects the WinFormHtmlEditor instance on the offer-letter form. In the Document Outline she drills into Toolbar1 - the top row, where the file and format buttons live. Toolbar1 is a regular ToolStrip, so the familiar plus-icon menu appears at the right end of the toolbar. She picks "Button", positions it just after the existing Save button, and assigns her custom 16x16 document icon to its Image property. ToolTipText becomes "Insert offer letter template". She gives it the name btnInsertOfferLetter.

Sarah adding a custom ToolStripButton onto Toolbar1 in the Visual Studio designer

Wiring the click handler

Sarah double-clicks the button. The designer drops her into an empty Click handler. She knows the editor's public surface from last year's work: htmlEditor1.Content.InsertHtml(...) drops HTML at the caret, pushes the change through the undo stack, and fires HtmlChanged. That is exactly what she wants - the recruiter should be able to undo the template insertion with one Ctrl+Z if they pick the wrong template by mistake.

private void BtnInsertOfferLetter_Click(object sender, EventArgs e)

{

    const string offerLetterTemplate =

        "<h2>Offer of Employment</h2>"

      + "<p>Dear [Candidate Name],</p>"

      + "<p>We are delighted to extend an offer of employment for the position "

      + "of [Role] at Contoso, Inc., reporting to [Hiring Manager].</p>"

      + "<p><strong>Base salary:</strong> $[Amount] annually.</p>"

      + "<p><strong>Equity:</strong> [Shares] options, vesting over four "

      + "years with a one-year cliff. Full terms attached.</p>"

      + "<p>This offer is contingent on satisfactory completion of a background "

      + "check and signed proprietary-information agreement.</p>";



    htmlEditor1.Content.InsertHtml(offerLetterTemplate, moveCaretAfterInsertedContent: true);

}

The Insert Offer Letter Template button rendered at runtime alongside the factory toolbar items

What Sarah didn't have to do

She did not have to fight the editor's layout. Toolbar1, Toolbar2, and ToolbarFooter are real ToolStrip objects - they accept ToolStripButton, ToolStripDropDownButton, ToolStripComboBox, and ToolStripSeparator children with no special API. She did not have to manage the undo stack manually; Content.InsertHtml handles that. And she did not have to worry about dirty-tracking - the editor's HtmlChanged event fires automatically.

For the equity disclaimer scare, Sarah's next iteration adds three more buttons: "Insert Standard Disclaimer", "Insert Equity Schedule", and "Insert Background Check Clause" - each dropping a different pre-approved block. Same pattern, three more handlers, ten more minutes of work.

The full toolkit Sarah can reach from a click handler

  • htmlEditor1.Content - insert HTML, set body HTML, open files, get the current document.
  • htmlEditor1.Formatting - apply bold, italic, font name, indent, lists, headings.
  • htmlEditor1.Selection - read or modify the current selection.
  • htmlEditor1.StateQuery - ask whether the caret is inside a table, hyperlink, image, etc.

By Friday Sarah's recruiters have shipped four offer letters using the new button. Nobody has forgotten the equity disclaimer.

Last updated on May 15, 2026