shadcn.io is not affiliated with official shadcn/ui
Contact Support Dialog Block for Radix UI
A shipments table with a support dialog shown open: a ticket form with a category, a priority and attached file names, and beside it up to three help articles picked by the category and the subject's words, each opening in place.
Installation
Overview
Answer the question before it becomes a ticket, with this support dialog for React with Radix UI. It opens from the Shipments page of Parcelfold, a shipping-label tool: the header holds an outline Contact support Button, and the page itself, a table of labels with a four-stop track each and a Help button in its top bar, is drawn under a soft scrim inside a hairline frame with a plus mark on each corner. The wide Dialog is a split. At the start are a category in a Toggle Group, a subject Input, a priority Native Select, a Textarea with a count and a File Upload that lists up to three files by name and size. At the end sit the answers that may help, up to three of ten typed articles, each a Collapsible with a "That solved it" button that closes the dialog without a ticket; on a phone they are a closed disclosure under the form. Send request, the only Shine Button, makes "Ticket T-2041" and gives the drawn Help button a count. For preview the dialog is drawn open in place when the page loads; the header button opens the real Dialog, with the same form and the same answers.
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 page: a native dialog element that isn't modal, so nothing is trapped, hidden or focused, and the page around the preview keeps working. The Contact support 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 SupportFields component and share the block's state, so what you typed 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.
How are the answers chosen?
By the category and the subject, in answersFor: an article in the chosen category scores 2, and each word of the subject that appears in its title or its keywords adds 3, so a subject can pull in an article from another category. The best three are shown, and a polite count, "3 answers may help", is said 600 ms after you stop typing. Each article opens in place, one at a time, and "That solved it" closes the dialog, says that no ticket was created and leaves the Help count where it was. In your app call your docs search here.
What happens to the attached files?
Nothing: they stay on your device. The File Upload lists each by name and size, up to three and under 10 MB each, with a Remove button; the dialog never reads a file, uploads it or makes a preview. The list is one line that scrolls sideways, so adding a file doesn't move the form. A file over the limit is skipped with a message. In your app upload the files from send to your storage and attach their ids to the ticket.
What does Send request do?
It checks the subject and the message, names what is missing under the field and puts focus in it, and otherwise makes "Ticket T-2041" in this demo, closes the dialog and gives the drawn Help button a count; the next one is T-2042. A polite message says what category it was about and that nothing was sent. The message has a 600-character limit with a count that is visible but not read out, and the screen reader hears only the stages: 20 to go, 10 to go, none.
Is it accessible from the keyboard?
Yes. The real dialog is named by its title and described by its sentence, focus stays inside until it closes, and Escape, Cancel and the close button return focus to Contact support. Arrow keys move between the four categories; the answers are buttons that open and close with Enter or Space. The trigger is the button wrapped in DialogTrigger with asChild, and the dialog is controlled with open and onOpenChange. The copy shown on load moves no focus.
What stays local in the demo, and how do I wire it up?
All of it. No ticket reaches anyone: the ticket numbers are typed counts, the ten articles are constants, and the Help count lives in the block's state. In your app create the ticket on your server from send, show its real number in the confirmation, and keep the answers' links to the same articles your help centre serves.