---
title: "Markdown keyboard keys: kbd tags and spans in pandoc"
slug: markdown-keyboard-keys
excerpt: "The input file and the reader commands behind these markdown keyboard keys runs are set out in Three ways to write a key."
quickAnswer: "Under Three ways to write a key, the input and three pandoc commands appear. Fragment output for the typed tags is quoted in Hand-typed kbd tags. In Bracketed spans with a kbd class, the fragment runs for the span line are compared reader by reader. For the backtick line, see Key names in a code span. A longer combo and a span with two classes get their runs in Three-key combos and an extra class. One markdown run on a list and a table is quoted in Spans in a list and a table. The added extension and its run sit in Switching bracketed_spans on for commonmark. Numbered grep steps are in Counting kbd tags with grep."
keyTakeaways:
  - "The section Hand-typed kbd tags quotes the line each reader printed for typed tags."
  - "Reader-by-reader output for the span line is laid out in Bracketed spans with a kbd class."
  - "See Key names in a code span for what the backtick line produced."
  - "Runs on a three-key combo and a second class are quoted in Three-key combos and an extra class."
  - "A grep recipe and the counts it printed are numbered in Counting kbd tags with grep."
publishedDate: 2026-10-21
dateModified: 2026-10-21
author: Bobby Huang
cluster: B
role: post
status: published
faq:
  - q: My bracketed key stayed as plain text. Why?
    a: "Reader outputs for the span line, with the extension list lines, are compared in Bracketed spans with a kbd class."
  - q: Does a code span give me a kbd element?
    a: "Output for the backtick line is quoted in Key names in a code span."
  - q: Could I get kbd elements while staying on commonmark?
    a: "A single run using the added extension is quoted in Switching bracketed_spans on for commonmark."
  - q: Do key spans work inside lists and tables?
    a: "The section Spans in a list and a table quotes one markdown run on a short list and a one-row table."
related:
  - raw-html-in-markdown
  - markdown-underline-text
  - markdown-text-color
---

Over these tests, pandoc 3.8.3 turned markdown keyboard keys into three shapes: a `<kbd>` element, a `<code>` element, or the source characters left untouched. Which shape you got hung on two things, how the key was typed and which reader ran.

One Mac ran each command once, under the `markdown`, `commonmark` and `commonmark_x` readers. What you'll read is pandoc's printed HTML, copied as it came. How a key might look on screen was never measured, so it stays out of scope.

## Three ways to write a key

The test file is `kbd.md`, and it holds one save shortcut written three ways. The file, as `cat kbd.md` printed it:

```text
Press <kbd>Ctrl</kbd>+<kbd>S</kbd> to save.

Press [Ctrl]{.kbd}+[S]{.kbd} to save.

Press `Ctrl+S` to save.
```

Line one wraps each key in hand-typed HTML tags. Line two relies on pandoc's `bracketed_spans` extension, with a `.kbd` class after each key name. Line three holds the whole shortcut in one code span.

Each reader got the file through its own command:

```sh
pandoc -f markdown -t html kbd.md
pandoc -f commonmark -t html kbd.md
pandoc -f commonmark_x -t html kbd.md
```

All three commands finished with rc 0. The output is an HTML fragment with one `<p>` for each of the three nonblank input lines, and the sections below take those lines one at a time.

## Hand-typed kbd tags

For line one, the three readers agreed. Each run printed:

```html
<p>Press <kbd>Ctrl</kbd>+<kbd>S</kbd> to save.</p>
```

Pandoc passed the tags along unchanged, and the plus sign between them stayed a bare character. In the extension lists that `pandoc --list-extensions` printed, `+raw_html` shows up for `markdown` at line 53, for `commonmark` at line 18 and for `commonmark_x` at line 18. That matches what the three runs did with the tags.

Hand-typed tags have a post of their own, [raw HTML in Markdown](raw-html-in-markdown.md), so this page goes no further with them.

## Bracketed spans with a kbd class

Line two gave one result under `markdown` and `commonmark_x` and another under plain `commonmark`. Here is the `markdown` result:

```html
<p>Press <kbd>Ctrl</kbd>+<kbd>S</kbd> to save.</p>
```

From `commonmark_x` came that same line, character for character. So for this input, `markdown` and `commonmark_x` each turned a span carrying `.kbd` into a `<kbd>` element, and neither one wrote a `<span>` tag for it.

Switching to plain `commonmark` produced:

```html
<p>Press [Ctrl]{.kbd}+[S]{.kbd} to save.</p>
```

That plain `commonmark` output kept the square brackets, the curly braces and the class names as ordinary paragraph text.

Each reader's extension list agrees with this result. In the `markdown` list, line 10 reads `+bracketed_spans`. The `commonmark_x` list has `+bracketed_spans` at line 5. The plain `commonmark` list has `-bracketed_spans` at line 5, a minus sign that marks it as off by default for that reader.

Between the three runs of this line, the input bytes stayed fixed and the `-f` value was the one thing that changed. That single swap moved the result from `<kbd>` elements under `markdown` and `commonmark_x` to bracket text under plain `commonmark`.

## Key names in a code span

Line three gave one result under `markdown`, `commonmark` and `commonmark_x` alike:

```html
<p>Press <code>Ctrl+S</code> to save.</p>
```

The whole shortcut, plus sign included, sat inside a single `<code>` element. No `<kbd>` element came from the backtick line under any of the three readers in this test. If your goal is key markup in the HTML, the code span route did not produce it in these runs.

## Three-key combos and an extra class

An extra script tried two more inputs. Both files sit in the test folder next to `kbd.md`, and each one went through `markdown`, `commonmark` and `commonmark_x` in turn. The file `kbd-combo.md` holds a three-key shortcut:

```text
Open the palette with [Ctrl]{.kbd}+[Shift]{.kbd}+[P]{.kbd} now.
```

Under `markdown`, pandoc printed:

```html
<p>Open the palette with <kbd>Ctrl</kbd>+<kbd>Shift</kbd>+<kbd>P</kbd>
now.</p>
```

Pandoc wrapped that output onto a second line before the word `now.`, and the `commonmark_x` run printed both lines exactly the same way. From plain `commonmark`, the line came back as text: `<p>Open the palette with [Ctrl]{.kbd}+[Shift]{.kbd}+[P]{.kbd} now.</p>`. With three spans, `markdown` and `commonmark_x` gave each key a separate `<kbd>`, just as they did with two.

The second file, `kbd-twoclass.md`, gives one span a pair of classes:

```text
Press [Esc]{.kbd .key} to close.
```

Here is what `markdown` returned:

```html
<p>Press <kbd class="key">Esc</kbd> to close.</p>
```

Under `markdown`, the `kbd` class turned into the element name, and `key` was kept as a `class` attribute on that element. The `commonmark_x` reader printed that identical line. Plain `commonmark` left the span as the text `[Esc]{.kbd .key}`. The six runs for these two files all returned rc 0.

## Spans in a list and a table

The same extra script checked spans inside block structures under `markdown` alone. The input `kbd-list.md` reads:

```text
- Save with [Ctrl]{.kbd}+[S]{.kbd}.
- Undo with [Ctrl]{.kbd}+[Z]{.kbd}.

| Action | Keys |
|---|---|
| Find | [Ctrl]{.kbd}+[F]{.kbd} |
```

This command converted it:

```sh
pandoc -f markdown -t html kbd-list.md
```

Pandoc printed:

```html
<ul>
<li>Save with <kbd>Ctrl</kbd>+<kbd>S</kbd>.</li>
<li>Undo with <kbd>Ctrl</kbd>+<kbd>Z</kbd>.</li>
</ul>
<table>
<thead>
<tr>
<th>Action</th>
<th>Keys</th>
</tr>
</thead>
<tbody>
<tr>
<td>Find</td>
<td><kbd>Ctrl</kbd>+<kbd>F</kbd></td>
</tr>
</tbody>
</table>
```

Under `markdown`, both list items and the table cell held `<kbd>` elements, and rc was 0. This file was not run through the other two readers, so the page makes no claim about them for lists or tables.

## Switching bracketed_spans on for commonmark

Pandoc lets you add an extension to a reader name with a `+`. For `kbd.md`, the extra script used this form:

```sh
pandoc -f commonmark+bracketed_spans -t html kbd.md
```

It printed:

```html
<p>Press <kbd>Ctrl</kbd>+<kbd>S</kbd> to save.</p>
<p>Press <kbd>Ctrl</kbd>+<kbd>S</kbd> to save.</p>
<p>Press <code>Ctrl+S</code> to save.</p>
```

That reader name was accepted, with rc 0. Once the extension was added to `commonmark`, the span line became `<kbd>` elements, and all three output lines match the `markdown` run of the same file.

## Counting kbd tags with grep

Each command below was issued by the extra script, with the test folder as its working directory. Replace `kbd.md` with your file's name to try them yourself.

```sh
cat kbd.md
pandoc -f markdown -t html kbd.md -o kbd-out-markdown.html
pandoc -f commonmark -t html kbd.md -o kbd-out-commonmark.html
pandoc -f commonmark_x -t html kbd.md -o kbd-out-commonmark_x.html
grep -c '<kbd>' kbd-out-markdown.html
grep -c '<kbd>' kbd-out-commonmark.html
grep -c '<kbd>' kbd-out-commonmark_x.html
grep -c '{.kbd}' kbd-out-commonmark.html
```

1. Check the `cat` output and mark the lines that carry a key.
2. Run the three `pandoc` lines, which save an HTML file for each reader. Each of them is logged with rc 0.
3. Count lines holding `<kbd>` in each file. Counts came back as `2` for `kbd-out-markdown.html`, `1` for `kbd-out-commonmark.html` and `2` for `kbd-out-commonmark_x.html`.
4. Search the plain `commonmark` file for a leftover class. That count came back `1`.

`grep -c` tallies lines that match, which means a line with two `<kbd>` elements still adds just one. In step 3, the gap between `2` and `1` is the span line: plain `commonmark` kept it as text, and step 4 found it there.

Another post from this test batch reports runs on [Markdown include file lines](markdown-include-file.md).

## Where Sashmark fits

Sashmark's design calls for correcting a slip directly within the formatted text in front of you. On saving, its Round-Trip would put down your fix while the surrounding bytes stay untouched. A separate Changes View feature, also in the plans, would mark passages an agent reworked after your previous look, git repository or not.

It is in development and has not been released. The app itself has no AI features. See the [planned features](/features), the [help pages](/help) and the [roadmap](/roadmap) for where each part stands.

More at [sashmark.com](https://www.sashmark.com/).
