shadcn.io is an independent project built on shadcn/ui
Edit Profile opens Edit profile. A gradient cover has buttons to change or remove it, and Margaret Villard's photo overlaps the corner. The fields show Margaret, Villard, margaret-villard-69 with a check, a website prefixed https://, and a biography with characters left.
Use it to edit a profile without a settings page. The check does not look the name up. A longer editor fits a Sheet. In Solid, Ark UI does not save the profile.
Installation
Anatomy

Usage
<Dialog>
<DialogTrigger asChild={props => <Button {...props()} variant="outline">Edit Profile</Button>} />
<DialogContent class="gap-0 p-0 sm:max-w-2xl">
<DialogHeader class="px-6 py-4">
<DialogTitle>Edit profile</DialogTitle>
</DialogHeader>
<div class="relative">
<div class="h-40 bg-linear-to-r from-primary/30 via-primary/15 to-muted" />
<div class="absolute end-4 top-4 flex gap-2">
<Button aria-label="Change cover image" pill size="icon-sm" variant="secondary">
<ImageIcon aria-hidden="true" />
</Button>
<Button aria-label="Remove cover image" pill size="icon-sm" variant="secondary">
<XIcon aria-hidden="true" />
</Button>
</div>
<div class="absolute start-6 -bottom-12">
<Avatar class="size-24 ring-4 ring-background">
<AvatarImage alt="Margaret Villard" src="https://github.com/shadcn.png" />
<AvatarFallback>MV</AvatarFallback>
</Avatar>
<Button aria-label="Change profile photo" class="absolute end-0 bottom-0" pill size="icon-sm">
<ImageIcon aria-hidden="true" />
</Button>
</div>
</div>
<FieldGroup class="px-6 pt-16 pb-6">
<div class="grid grid-cols-2 gap-4">
<Field>
<FieldLabel>First name</FieldLabel>
<Input value="Margaret" name="first-name" type="text" />
{/* The rest of the file is under Installation. */}FAQ
How do the image buttons upload?
They're icon buttons with an aria-label and no handler. Have each open a hidden file input with accept="image/*", preview the file, and upload it when people save.
Does the check mean the username is available?
It's a static text-success check in an InputGroupAddon. Check availability as people type, with a short debounce, and swap the icon for a spinner or an error while it runs.
How does the character counter work?
The textarea is controlled with maxLength of 200, and a FieldDescription under it shows 200 minus the current length.
How is the https:// prefix built?
An InputGroup with the InputGroupInput and an InputGroupAddon holding InputGroupText. The addon comes after the input in the markup, so clicking it focuses the input, and align="inline-start" shows it first.
API Reference
Dialog
Sets asChild and props. The rest is on the Dialog page.
Button
Sets props, variant="outline", aria-label="Change cover image", pill, size="icon-sm" and variant="secondary". The rest is on the Button page.
Avatar
Sets alt="Margaret Villard" and src="https://github.com/shadcn.png". The rest is on the Avatar page.
Field
No extra props in this demo. The rest is on the Field page.
Input
Sets value="Margaret", name="first-name", type="text", value="Villard" and name="last-name". The rest is on the Input page.
Input Group
Sets name="username", onInput={handleUsernameChange}, type="text", value={username()}, align="inline-end" and value="www.margaret.com". The rest is on the Input Group page.
Textarea
Sets maxLength={maxBioLength}, name="bio", onInput={handleBioChange} and value={bio()}. The rest is on the Textarea page.





