Copy as Markdown
The Copy as Markdown button lets a reader copy the canonical article as Markdown instead of pasting rendered HTML into a prompt. The pattern follows thoughtbot’s Copy as Markdown button and fits these sites because the source of truth is already Markdown: articles should be easy to read in a browser, but also easy to hand to an assistant, quote in a working note, or archive as plain text.
The button below is the live component; it copies this page’s own Markdown output to the clipboard and briefly swaps its label to Copied.
When to use#
- Article pages — blog posts and TIL notes — get the button automatically in the page header; there is nothing to declare per page
- The reader should be able to hand the canonical article to an assistant, quote it in a working note, or archive it as plain text
- The feature needs no account, no API, and no special integration: a durable plain-text version of the page and a button for readers who want the Markdown directly
- A page outside blog and TIL can force the button on or off with
showCopyMarkdownunder thepageHeaderfront matter key - On blog and TIL pages the head also advertises the Markdown representation to machines, whether or not a reader ever presses the button
Implementation#
The site configuration registers the output, and every regular page renders it:
mediaTypes:
text/markdown:
suffixes:
- md
outputFormats:
Markdown:
mediaType: text/markdown
rel: alternate
isPlainText: true
notAlternative: false
outputs:
page:
- HTML
- MarkdownThe Markdown template, layouts/_default/single.markdown.md, emits a small front matter block and then the page’s raw Markdown content:
---
title: {{ .Title | jsonify }}
lastmod: {{ .Lastmod.Format "2006-01-02" | jsonify }}
canonical: {{ .Permalink | jsonify }}
---
{{ .RawContent }}The page header renders the button through the shared partial:
{{ partial "copy-as-markdown.html" $page }}<button type="button" class="copy-markdown-button" aria-label="Copy as Markdown" title="Copy as Markdown" data-copy-markdown-url="/en/docs/site-implementation/components/copy-as-markdown/index.md" data-copy-label="Copy as Markdown" data-copied-label="Copied" data-error-label="Could not copy">
<img class="copy-markdown-icon copy-markdown-icon-light" src="/images/markdown-mark-solid.svg" alt="" aria-hidden="true" />
<img class="copy-markdown-icon copy-markdown-icon-dark" src="/images/markdown-mark.svg" alt="" aria-hidden="true" />
<span class="sr-only" aria-live="polite">Copy as Markdown</span>
</button>The handler is the document-level click listener in assets/js/site.js bound to [data-copy-markdown-url]. It fetches that URL, copies the response through the shared copyText helper — the same one code-block copying uses, so there is only one fallback path for older browsers — and briefly swaps the button’s label, aria-label, and title.
| Attribute or class | Role |
|---|---|
data-copy-markdown-url | URL of the page’s Markdown output; also what the handler matches |
data-copy-label | Resting label, restored after the swap |
data-copied-label | Label shown after a successful copy |
data-error-label | Label shown when the fetch or the copy fails |
copy-markdown-button-done | Class toggled on success |
copy-markdown-button-error | Class toggled on failure |
The shared partial is layouts/partials/copy-as-markdown.html. It renders only when the page has a Markdown output, takes the real output URL from Hugo — so the JavaScript never guesses whether the Markdown lives at post.md or post/index.md — and pulls its three labels from i18n. layouts/partials/entry-title-block.html turns the button on for blog posts and TIL notes, and layouts/_default/baseof.html adds <link rel="alternate" type="text/markdown" href="…"> to the head of those pages. There is no dedicated stylesheet; the button classes live in assets/css/site.css.
Interface manifest
- Kind
- Component
- Category
- Action
- Status
- Implemented
- Implementation
- Partial:
layouts/partials/copy-as-markdown.htmllayouts/partials/entry-title-block.htmlLayout:layouts/_default/baseof.htmllayouts/_default/single.markdown.mdCSS:assets/css/site.cssJavaScript:assets/js/site.jsi18n:copyAsMarkdowncopiedMarkdowncopyMarkdownErrorConfiguration:config/_default/hugo.yml
Accessibility#
- The control is a native
<button type="button">, so click, Enter, and Space all activate it with no custom key handling - The icon-only button carries its accessible name three ways:
aria-label, the visually hiddensr-onlyspan, and a matchingtitle; both icon images are decorative, with emptyaltandaria-hidden="true" - The
sr-onlylabel span carriesaria-live="polite", so the swap toCopied— or to the error label — is announced by screen readers rather than shown only visually - Avoid treating the
copy-markdown-button-doneandcopy-markdown-button-errorclasses as the feedback; they are styling hooks, and the label swap is what makes the result perceivable