Implementing a One-Click Copy Button for MDX Code Blocks in Next.js

A detailed guide on adding a copy button to MDX code blocks in your technical blog using React and modern best practices.


Implementing a One-Click Copy Button for MDX Code Blocks

This document describes how to add a one-click copy button to code blocks in an MDX technical documentation blog built with Next.js.

Background

In many blogs, code blocks are typically wrapped with triple backticks and rendered as <pre><code> elements. To enhance user experience, we aim to place a copy button at the top-right corner of each code block, enabling users to copy the code with a single click.

Initial Approach

Initially, we used a useEffect hook (or custom hook) on the client side to scan all <pre> elements and dynamically inject the copy button. This required minimal changes to the existing code structure.

Improved Approach: Using <figure> as the Positioning Container

When using tools like rehype-pretty-code, code blocks are often wrapped in a <figure data-rehype-pretty-code-figure> element. Instead of inserting the copy button directly into <pre>, we can place it inside <figure>. This avoids potential layout issues such as scrollbars or overflow on <pre>.

  1. Target <figure> Instead of <pre>
    Search for:

    const figures = rootElement.querySelectorAll(
      "figure[data-rehype-pretty-code-figure]"
    );

    and append the button to the <figure> element rather than the <pre> element.

  2. Set <figure> to position: relative;
    In your global CSS (e.g., globals.css), add:

    figure[data-rehype-pretty-code-figure] {
      position: relative;
    }

    This ensures that the button, with absolute positioning, will be positioned relative to the <figure>.

  3. Define a .copy-button Class with Tailwind's @apply
    To avoid issues with Tailwind Purge, define the button styles in your global CSS:

    .copy-button {
      @apply absolute top-2 right-2 bg-gray-700 text-white rounded px-2 py-1 text-sm opacity-0 group-hover:opacity-100 transition;
    }

    Then, in your JavaScript/TypeScript code, simply add the class to the button:

    button.classList.add("copy-button");

Handling Style Conflicts

If other plugins or global styles interfere with the button’s positioning:

  • Use browser DevTools to ensure that the <figure> element has position: relative; and that the button receives the correct computed styles (e.g., position: absolute; top: 0.5rem; right: 0.5rem;).
  • Adjust the styles on the <pre> element if necessary. For example:
    figure[data-rehype-pretty-code-figure] pre {
      overflow: auto;
      margin: 0;
      padding: 1rem;
    }
  • Verify that the Tailwind classes are not purged by using the defined .copy-button class with @apply.

Conclusion

By injecting the copy button into the <figure> element and ensuring it has a proper positioning context and dedicated styling, we can achieve a clean, one-click copy solution for MDX code blocks. This approach aligns with modern React and Next.js best practices, providing a solid foundation for future enhancements.