shadcn.io is not affiliated with official shadcn/ui
Bookmark Folder Dialog Block for Radix UI
A read-later app's article page with its bookmark dialog shown open: an editable title, a folder tree with a New folder field, tags, a note with a counter, a reminder choice and a line that spells the path it will save to.
Installation
Overview
Keep a page where you will find it again, with this bookmark dialog for React with Radix UI. It opens from an essay on Lanternread, a read-later app: the header holds an outline Save Button that reads "Saved" once a bookmark exists, and the reading list, a rail of three folders with their counts beside rows of saved pages, is drawn under a soft scrim inside a hairline frame with a plus mark on each corner. The Dialog has the page's title in an Input with its address under it, then a Tree View of three folders with two inside each, one picked, and a field that adds a folder inside the branch you are in and refuses a name that is already there. Tags are pills you switch on, the note is a Textarea with a count of what is left, and a Select sets a reminder: Never, Tonight or This weekend. A line above the buttons spells the path, "Saving to Reading / Long reads". Save bookmark, the only Shine Button, puts the page at the top of the drawn list and raises its folder's count. 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 Save 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 BookmarkFields component and share the block's state, so a folder 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.
How does the folder tree work?
It is the catalog Tree View with one level of folders inside each top-level one. The arrow keys walk it, the side arrows open and close a section, Home and End jump, and the picked folder gets a bar at its start and a tint, so it never relies on colour. The tree keeps its height and scrolls inside, so a long folder list cannot push Save bookmark away. Folders are leaves that look like links but stay out of the tab order: the tree is one tab stop.
Where does a new folder go?
Inside the top-level folder of your selection, whether you picked it or one of its folders. The field's placeholder and the line under it name that folder. Add, or Enter in the field, creates the folder, opens the section, picks the new folder and says so; Enter never saves the bookmark. A name that is empty or already in that section is refused in place and focus stays in the field. Names stop at 24 characters, and folders nest one level here.
What does Save bookmark do, and what stays local?
Here it puts a row with your title at the top of the drawn reading list, raises the folder's count and its parents' counts, and says in a polite status message that nothing was sent. Saving again moves the same page rather than adding a second row. Tags, note and reminder are kept in this page's state only and no reminder is set. In your app post the title, address, folder, tags, note and reminder from save, create new folders on the server before saving into them, and read the counts back.
What if the title is empty?
Save bookmark names the problem under the field, "Give the bookmark a title.", marks the field invalid and puts focus back in it. The line holds the address the rest of the time, in the same slot, so nothing moves. The error clears as soon as you type.
Is it accessible from the keyboard?
Yes. The real dialog is named by its title and described by its sentence, focus starts in the title field and stays inside until it closes, and Escape, Cancel and the close button all return focus to Save. The trigger is the Save 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 Save.
How does it look on a phone?
The copy shown on load docks to the foot of the page as a bottom sheet with Cancel and Save bookmark pinned at the bottom, and the fields, the tree and the tags scrolling above them. The real dialog keeps the Dialog's own phone layout: a sheet stuck to the bottom of the screen.