shadcn.io is not affiliated with official shadcn/ui
Copy To Clipboard Dialog Block for Radix UI
A stock spreadsheet with its copy-as dialog shown open: the same four selected rows as Markdown, CSV, JSON or plain text, a preview that shows exactly what lands on the clipboard, a header-row option and a button that writes it.
Installation
Overview
Paste the same rows into whatever comes next, with this copy-as dialog for React with Radix UI. It opens from the Q3 stock count of Gridnote, a spreadsheet app: the header holds an outline Copy as Button, and the sheet, a toolbar over a six-column grid with four selected rows, is drawn under a soft scrim inside a hairline frame with a plus mark on each corner. The Dialog offers the rows in four formats through a Segment Group: Markdown, CSV, JSON and Plain text, which is tab-separated so it pastes into another sheet as cells. A sentence says what each format is for, a checkbox includes the header row where the format allows it, and a monospace preview rewrites on every pick and shows exactly what goes on the clipboard: CSV fields with commas or quotes are quoted, pipes are escaped in Markdown. Copy 4 rows as CSV, the only Shine Button, writes the real clipboard, its label follows the format, and it shows Copied for a moment. The selection on the sheet behind turns into a dashed marquee. For preview the dialog is drawn open in place when the page loads; the header button opens the real Dialog.
FAQ
Why is the dialog already open, and what do I keep?
So you can see it without a click. On load the dialog is a copy drawn in place over the sheet: a native dialog element that isn't modal, so nothing is trapped, hidden or focused, and the page around the preview keeps working. The Copy as button opens the real Dialog, modal, with its focus trap, Escape and focus return, and the copy stays closed until a reload. Both show the same CopyFields component and share the block's state, so a format picked in the copy is there in the real one. In your app keep the Dialog and drop the copy: the "in-place" state, the scrim and the dialog element inside the frame.
Does it really copy, and what if the browser refuses?
Yes: Copy writes the preview text to the clipboard with the browser's clipboard API, which needs a secure page (HTTPS or localhost) and, in some browsers, a press as its trigger, which this is. If the browser refuses, the button says it couldn't copy, an alert line asks you to select the preview and copy it yourself, and a polite message says so. After a success the button shows Copied and the number of rows for a few seconds, then returns to its label; nothing closes by itself.
How are the formats built?
Pure functions over the typed rows, one per format. csvOf quotes a field when it holds a comma, a quote or a line break, doubles the quotes inside it and puts an apostrophe in front of a field that starts with a formula sign, so a spreadsheet never runs it. markdownOf writes a pipe table with a separator row and right-aligned numbers. jsonOf writes an array of objects with named fields. textOf joins cells with tabs. Swap ROWS and COLUMNS for your selection and add formats as entries in CHOICES.
Why can't I turn the header row off for Markdown and JSON?
A Markdown table needs its header row to be a table, and JSON names every field in every object, so there is no header to leave out. For those two the checkbox is ticked, disabled and says "always, here". For CSV and Plain text it is yours to choose, and the preview and the line count change with it.
Is it accessible from the keyboard?
Yes. The formats are one radio group, so the arrow keys move between them, the checkbox is a real one, and the preview is a focusable region you can scroll with the arrow keys. A polite message reports each copy, and the dialog has its own copy of that message while it is open. The trigger is the Copy as button wrapped in DialogTrigger with asChild, and the dialog is controlled with open and onOpenChange. Focus returns to Copy as when the dialog closes.
How does it look on a phone?
The copy shown on load docks to the foot of the page as a bottom sheet, its actions pinned at the bottom with the copy button on top, and the formats in two rows, the checkbox and the preview scrolling above. The preview scrolls sideways inside its box rather than wrapping. The real dialog keeps the Dialog's own phone layout: a sheet stuck to the bottom of the screen.