shadcn.io is not affiliated with official shadcn/ui
Link Insert Dialog Block for Svelte
A writing app's editor with its link dialog shown open: an address field that checks https, http, mailto and page paths with a message for each failure, link text from the selection, four pages to press, a new-tab switch with its HTML, and Remove link.
Installation
Overview
Turn selected words into a link without leaving the page, with this link dialog for Svelte with Ark UI. It opens from a draft in Scribeloft, a writing app, with the words "release notes" selected: the header holds an outline Insert link Button that becomes Edit link once the words are a link, and the editor, a toolbar with the link tool lit, a title bar, the sentence with the selection highlighted and paragraph bars, is drawn under a soft scrim inside a hairline frame with a plus mark on each corner. The Dialog has an address Input in a Field that accepts https, http, mailto and a page path, and says which part is wrong, and a Link text input pre-filled from the selection. Four of the app's own pages are buttons that fill the address. An Open in a new tab Switch updates a code line with the HTML that would be written, and Remove link appears once a link exists. Apply, the only Shine Button, underlines the words in the drawn sentence and puts the address in their title. 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 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 Insert link 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 LinkFields component and share the block's state, so an address 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.
Which addresses does it accept, and what does it say about the rest?
Four kinds. A page path starts with one slash, an https or http address needs a site name with a dot, a mailto address needs an email, and nothing else is allowed. Each failure has its own message: nothing typed, spaces in the address, "Add an email after mailto:.", "Add a site name, like https://example.com.", "Only https, http, mailto and page paths work." for schemes such as javascript, and "Start with https:// or pick a page below." for a bare domain. The check runs when you leave the field and when you apply, and once an error shows it follows your typing. problemWith is the one place to change the rules.
What is the code line for?
It shows the HTML the dialog would write, target="_blank" rel="noopener" included when Open in a new tab is on, with quotes in the address escaped. It is there so the switch has a visible consequence. In your editor, store the link in your document model instead and render the anchor yourself, and keep rel="noopener" for anything that opens a new tab.
How does editing and removing work?
After Apply the header button reads Edit link, and opening the dialog again starts from the saved address, text and tab choice. Remove link is held in the layout from the start, hidden and inert until a link exists, so the dialog never changes height. It makes the words plain again and says so in the polite status message. Closing without Apply discards what you typed.
What stays local in the demo?
All of it. Nothing is fetched or checked online: the address is only read for its shape, so a well-formed address that does not exist is accepted. The page buttons list four typed pages. In your app fill that list from your router, check internal pages against it, and decide whether to add a protocol to a bare domain instead of refusing it.
Is it accessible from the keyboard?
Yes. The real dialog is named by its title and described by its sentence, focus starts in the address field and stays inside until it closes, and Escape, Cancel and the close button all return focus to Insert link. The trigger is the Insert link button wrapped in DialogTrigger with asChild, and the dialog is controlled with open and onOpenChange. The copy shown on load moves no focus; Escape inside it, Cancel and its close button close it and put focus on Insert link. The page buttons report their chosen state to assistive technology, and the code line scrolls sideways when it is long.