File upload
Drop zone + file list.
- Browsers
- Chrome 123+Firefox 121+Safari 17.5+
- Tier
- 1
- Bundle compressed
- 2.27 kB
- Forced layout budget
- 0 ms
- Axe budget
- 0
- SSR compatible
- Yes
- Published contract
- Test harness
Playground
File upload
Attachments
"use client";
import { FileUpload } from "@gunkin/uiify";
export default function Example() {
return (
<FileUpload.Root multiple maxFiles={3} maxSize={5000000} className="">
<FileUpload.HiddenInput name="attachments" aria-label="Attachments" />
<FileUpload.Trigger as="button" className="">
Choose files
</FileUpload.Trigger>
<FileUpload.Dropzone className="">Drop files here</FileUpload.Dropzone>
<FileUpload.List className="">
{(files) =>
files.map((file, index) => (
<FileUpload.Item key={file.name} index={index} className="">
<span>
{file.name} · {(file.size / 1024).toFixed(1)} KB
</span>
<FileUpload.ItemRemove index={index} aria-label={"Remove " + file.name}>
×
</FileUpload.ItemRemove>
</FileUpload.Item>
))
}
</FileUpload.List>
</FileUpload.Root>
);
}/* File upload — trigger button with dashed dropzone. */
@layer components {
[data-uiify-file-upload] {
display: grid;
gap: var(--space-md);
inline-size: 100%;
max-inline-size: 24rem;
}
[data-uiify-file-upload] [data-part="dropzone"] {
display: flex;
align-items: center;
justify-content: center;
min-block-size: 8rem;
padding: var(--space-lg);
border: 2px dashed var(--input);
border-radius: var(--radius-xl);
corner-shape: squircle;
background-color: var(--secondary);
color: var(--muted-foreground);
font-size: var(--text-sm);
text-align: center;
transition: var(--transition-colors);
}
[data-uiify-file-upload] [data-part="dropzone"]:hover,
[data-uiify-file-upload] [data-part="dropzone"][data-dragging] {
border-color: var(--primary);
background-color: color-mix(in oklch, var(--primary) 8%, transparent);
color: var(--foreground);
}
[data-uiify-file-upload] [data-part="dropzone"][data-disabled] {
opacity: 0.5;
cursor: not-allowed;
}
[data-uiify-file-upload] [data-part="list"] {
display: grid;
gap: var(--space-xs);
margin: 0;
padding: 0;
list-style: none;
}
[data-uiify-file-upload] [data-part="item"] {
display: flex;
align-items: center;
gap: var(--space-sm);
padding: var(--space-sm) var(--space-md);
border: var(--border-width) solid var(--border);
border-radius: var(--radius-lg);
corner-shape: squircle;
background-color: var(--card);
font-size: var(--text-sm);
}
}
@layer components {
[data-uiify-file-upload] [data-part="file-info"] {
display: grid;
gap: 0.25rem;
min-inline-size: 0;
flex: 1;
overflow-wrap: anywhere;
}
[data-uiify-file-upload] small {
color: var(--muted-foreground);
}
[data-uiify-file-upload] [data-part="remove"] {
display: grid;
place-items: center;
flex: none;
inline-size: 2rem;
block-size: 2rem;
margin-inline-start: auto;
border: 0;
border-radius: 50%;
background: var(--secondary);
color: var(--foreground);
font-size: 1.25rem;
line-height: 1;
cursor: pointer;
}
[data-uiify-file-upload] [data-part="remove"]:hover {
background: var(--accent);
}
[data-uiify-file-upload] [data-part="remove"]:focus-visible {
outline: 2px solid var(--ring);
outline-offset: 2px;
}
}"use client";
import { FileUpload } from "@gunkin/uiify";
export default function Example() {
return (
<FileUpload.Root
multiple
maxFiles={3}
maxSize={5000000}
className="
grid gap-md [inline-size:100%] [max-inline-size:24rem] [&_[data-part=dropzone]]:flex
[&_[data-part=dropzone]]:items-center [&_[data-part=dropzone]]:justify-center
[&_[data-part=dropzone]]:[min-block-size:8rem] [&_[data-part=dropzone]]:p-lg
[&_[data-part=dropzone]]:[border-width:2px] [&_[data-part=dropzone]]:[border-style:dashed]
[&_[data-part=dropzone]]:border-input [&_[data-part=dropzone]]:rounded-xl
[&_[data-part=dropzone]]:[corner-shape:squircle] [&_[data-part=dropzone]]:bg-secondary
[&_[data-part=dropzone]]:text-muted-foreground [&_[data-part=dropzone]]:text-sm
[&_[data-part=dropzone]]:text-center [&_[data-part=dropzone]]:[transition:var(--transition-colors)]
[&_[data-part=dropzone]:hover]:border-primary
[&_[data-part=dropzone]:hover]:[background-color:color-mix(in_oklch,_var(--primary)_8%,_transparent)]
[&_[data-part=dropzone]:hover]:text-foreground [&_[data-part=dropzone][data-dragging]]:border-primary
[&_[data-part=dropzone][data-dragging]]:[background-color:color-mix(in_oklch,_var(--primary)_8%,_transparent)]
[&_[data-part=dropzone][data-dragging]]:text-foreground [&_[data-part=dropzone][data-disabled]]:opacity-50
[&_[data-part=dropzone][data-disabled]]:cursor-not-allowed [&_[data-part=list]]:grid [&_[data-part=list]]:gap-xs
[&_[data-part=list]]:m-0 [&_[data-part=list]]:p-0 [&_[data-part=list]]:list-none [&_[data-part=item]]:flex
[&_[data-part=item]]:items-center [&_[data-part=item]]:gap-sm
[&_[data-part=item]]:[padding:var(--space-sm)_var(--space-md)]
[&_[data-part=item]]:border-[length:var(--border-width)] [&_[data-part=item]]:border-solid
[&_[data-part=item]]:border-border [&_[data-part=item]]:rounded-lg [&_[data-part=item]]:[corner-shape:squircle]
[&_[data-part=item]]:bg-card [&_[data-part=item]]:text-sm [&_[data-part=file-info]]:grid
[&_[data-part=file-info]]:[gap:0.25rem] [&_[data-part=file-info]]:[min-inline-size:0]
[&_[data-part=file-info]]:[flex:1] [&_[data-part=file-info]]:[overflow-wrap:anywhere]
[&_small]:text-muted-foreground [&_[data-part=remove]]:grid [&_[data-part=remove]]:place-items-center
[&_[data-part=remove]]:flex-none [&_[data-part=remove]]:[inline-size:2rem]
[&_[data-part=remove]]:[block-size:2rem] [&_[data-part=remove]]:[margin-inline-start:auto]
[&_[data-part=remove]]:border-0 [&_[data-part=remove]]:border-current [&_[data-part=remove]]:border-none
[&_[data-part=remove]]:rounded-[50%] [&_[data-part=remove]]:bg-secondary [&_[data-part=remove]]:text-foreground
[&_[data-part=remove]]:[font-size:1.25rem] [&_[data-part=remove]]:leading-none
[&_[data-part=remove]]:cursor-pointer [&_[data-part=remove]:hover]:bg-accent
[&_[data-part=remove]:focus-visible]:[outline:2px_solid_var(--ring)]
[&_[data-part=remove]:focus-visible]:[outline-offset:2px]
"
>
<FileUpload.HiddenInput name="attachments" aria-label="Attachments" />
<FileUpload.Trigger
as="button"
className={
"focus-visible:[outline:none] focus-visible:[box-shadow:0_0_0_var(--ring-width)_color-mix(in_oklch,_var(--" +
"ring)_16%,_transparent)] [--uiify-button-rim:rgb(255_255_255_/_20%)] [--uiify-button-wash:light-dark(rgb(" +
"255_255_255_/_15%),_rgb(255_255_255_/_30%))] relative isolate inline-flex items-center justify-center [ga" +
"p:0.375rem] [min-block-size:44px] [min-inline-size:44px] py-0 [padding-inline:1.25rem] border-[length:var" +
"(--border-width)] border-solid border-transparent rounded-[var(--radius-full)] bg-foreground bg-clip-padd" +
"ing text-background text-base font-[number:var(--weight-medium)] leading-6 no-underline cursor-pointer [t" +
"ransition:var(--transition-colors)] before:content-[''] before:absolute before:inset-0 before:z-[-1] befo" +
"re:[border-radius:inherit] before:pointer-events-none after:content-[''] after:absolute after:inset-0 aft" +
"er:z-[-1] after:[border-radius:inherit] after:pointer-events-none before:[background-color:var(--uiify-bu" +
"tton-wash)] before:[filter:blur(10px)] before:[translate:0_-50%] before:[clip-path:inset(50%_0_-50%_0_rou" +
"nd_var(--radius-full))] after:[padding:0.5px] after:[background-image:linear-gradient(to_bottom,_var(--ui" +
"ify-button-rim),_transparent_75%)] after:[mask:linear-gradient(#fff_0_0)_content-box_exclude,_linear-grad" +
"ient(#fff_0_0)] [&:is([data-size=sm],[data-size=icon-sm])]:[min-block-size:32px] [&:is([data-size=sm],[da" +
"ta-size=icon-sm])]:[min-inline-size:32px] [&:is([data-size=sm],[data-size=icon-sm])]:[padding-inline:0.75" +
"rem] [&:is([data-size=sm],[data-size=icon-sm])]:[gap:0.25rem] [&:is([data-size=sm],[data-size=icon-sm])]:" +
"text-sm [&:is([data-size=sm],[data-size=icon-sm])]:leading-5 [&:is([data-size=lg],[data-size=icon-lg])]:[" +
"min-block-size:56px] [&:is([data-size=lg],[data-size=icon-lg])]:[min-inline-size:56px] [&:is([data-size=l" +
"g],[data-size=icon-lg])]:[padding-inline:1.75rem] [&:is([data-size=lg],[data-size=icon-lg])]:text-lg [&:i" +
"s([data-size=lg],[data-size=icon-lg])]:leading-7 [&[data-size^=icon]]:px-0 [@media_(any-hover:_hover)_and" +
"_(any-pointer:_fine)]:hover:[&:not(:disabled,[data-disabled])]:[background-color:color-mix(in_oklch,_var(" +
"--foreground)_90%,_var(--background))] active:[&:not(:disabled,[data-disabled])]:[background-color:color-" +
"mix(in_oklch,_var(--foreground)_80%,_var(--background))] disabled:opacity-50 disabled:cursor-not-allowed " +
"data-[disabled]:opacity-50 data-[disabled]:cursor-not-allowed data-[variant=secondary]:[&:where(:not(:che" +
"cked+*,[data-selected],[aria-pressed=true],[aria-current=page]))]:[--uiify-button-rim:light-dark(rgb(0_0_" +
"0_/_5%),_rgb(255_255_255_/_10%))] data-[variant=secondary]:[&:where(:not(:checked+*,[data-selected],[aria" +
"-pressed=true],[aria-current=page]))]:[--uiify-button-wash:light-dark(rgb(255_255_255_/_20%),_rgb(255_255" +
"_255_/_5%))] data-[variant=secondary]:[&:where(:not(:checked+*,[data-selected],[aria-pressed=true],[aria-" +
"current=page]))]:[background-color:color-mix(in_oklch,_var(--foreground)_10%,_transparent)] data-[variant" +
"=secondary]:[&:where(:not(:checked+*,[data-selected],[aria-pressed=true],[aria-current=page]))]:text-fore" +
"ground data-[variant=outline]:[&:where(:not(:checked+*,[data-selected],[aria-pressed=true],[aria-current=" +
"page]))]:[--uiify-button-rim:color-mix(in_oklch,_var(--foreground)_25%,_transparent)] data-[variant=outli" +
"ne]:[&:where(:not(:checked+*,[data-selected],[aria-pressed=true],[aria-current=page]))]:[border-color:col" +
"or-mix(in_oklch,_var(--foreground)_20%,_transparent)] data-[variant=outline]:[&:where(:not(:checked+*,[da" +
"ta-selected],[aria-pressed=true],[aria-current=page]))]:bg-transparent data-[variant=outline]:[&:where(:n" +
"ot(:checked+*,[data-selected],[aria-pressed=true],[aria-current=page]))]:text-foreground data-[variant=ou" +
"tline]:before:content-none data-[variant=outline]:after:[inset:calc(-1_*_var(--border-width))] data-[vari" +
"ant=outline]:after:[padding:var(--border-width)] data-[variant=outline]:[&:where(:not(:checked+*,[data-se" +
"lected],[aria-pressed=true],[aria-current=page]))]:active:[&:not(:disabled,[data-disabled])]:[background-" +
"color:color-mix(in_oklch,_var(--foreground)_16%,_transparent)] data-[variant=ghost]:[&:where(:not(:checke" +
"d+*,[data-selected],[aria-pressed=true],[aria-current=page]))]:bg-transparent data-[variant=ghost]:[&:whe" +
"re(:not(:checked+*,[data-selected],[aria-pressed=true],[aria-current=page]))]:text-foreground data-[varia" +
"nt=ghost]:before:content-none data-[variant=ghost]:after:content-none data-[variant=ghost]:[&:where(:not(" +
":checked+*,[data-selected],[aria-pressed=true],[aria-current=page]))]:active:[&:not(:disabled,[data-disab" +
"led])]:[background-color:color-mix(in_oklch,_var(--foreground)_16%,_transparent)] data-[variant=secondary" +
"]:[&:where(:not(:checked+*,[data-selected],[aria-pressed=true],[aria-current=page]))]:active:[&:not(:disa" +
"bled,[data-disabled])]:[background-color:color-mix(in_oklch,_var(--foreground)_26%,_transparent)] data-[v" +
"ariant=destructive]:[&:where(:not(:checked+*,[data-selected],[aria-pressed=true],[aria-current=page]))]:[" +
"--uiify-button-wash:rgb(255_255_255_/_15%)] data-[variant=destructive]:[&:where(:not(:checked+*,[data-sel" +
"ected],[aria-pressed=true],[aria-current=page]))]:bg-destructive data-[variant=destructive]:text-destruct" +
"ive-foreground data-[variant=destructive]:[&:where(:not(:checked+*,[data-selected],[aria-pressed=true],[a" +
"ria-current=page]))]:active:[&:not(:disabled,[data-disabled])]:[background-color:color-mix(in_oklch,_var(" +
"--destructive),_black_20%)] [@media_(any-hover:_hover)_and_(any-pointer:_fine)]:data-[variant=outline]:[&" +
":where(:not(:checked+*,[data-selected],[aria-pressed=true],[aria-current=page]))]:hover:[&:not(:disabled," +
"[data-disabled])]:[background-color:color-mix(in_oklch,_var(--foreground)_10%,_transparent)] [@media_(any" +
"-hover:_hover)_and_(any-pointer:_fine)]:data-[variant=ghost]:[&:where(:not(:checked+*,[data-selected],[ar" +
"ia-pressed=true],[aria-current=page]))]:hover:[&:not(:disabled,[data-disabled])]:[background-color:color-" +
"mix(in_oklch,_var(--foreground)_10%,_transparent)] [@media_(any-hover:_hover)_and_(any-pointer:_fine)]:da" +
"ta-[variant=secondary]:[&:where(:not(:checked+*,[data-selected],[aria-pressed=true],[aria-current=page]))" +
"]:hover:[&:not(:disabled,[data-disabled])]:[background-color:color-mix(in_oklch,_var(--foreground)_20%,_t" +
"ransparent)] [@media_(any-hover:_hover)_and_(any-pointer:_fine)]:data-[variant=destructive]:[&:where(:not" +
"(:checked+*,[data-selected],[aria-pressed=true],[aria-current=page]))]:hover:[&:not(:disabled,[data-disab" +
"led])]:[background-color:color-mix(in_oklch,_var(--destructive),_black_12%)] [&:is([data-selected],[aria-" +
"pressed=true],[aria-current=page])]:[--uiify-button-rim:rgb(255_255_255_/_20%)] [&:is([data-selected],[ar" +
"ia-pressed=true],[aria-current=page])]:[--uiify-button-wash:rgb(255_255_255_/_15%)] [&:is([data-selected]" +
",[aria-pressed=true],[aria-current=page])]:border-transparent [&:is([data-selected],[aria-pressed=true],[" +
"aria-current=page])]:bg-primary [&:is([data-selected],[aria-pressed=true],[aria-current=page])]:text-prim" +
"ary-foreground [:checked+&]:[--uiify-button-rim:rgb(255_255_255_/_20%)] [:checked+&]:[--uiify-button-wash" +
":rgb(255_255_255_/_15%)] [:checked+&]:border-transparent [:checked+&]:bg-primary [:checked+&]:text-primar" +
"y-foreground [@media_(any-hover:_hover)_and_(any-pointer:_fine)]:[&:is([data-selected],[aria-pressed=true" +
"],[aria-current=page]):hover:not(:disabled,[data-disabled])]:[background-color:color-mix(in_oklch,_var(--" +
"primary)_88%,_var(--primary-foreground))] [@media_(any-hover:_hover)_and_(any-pointer:_fine)]:[:checked:n" +
"ot(:disabled)+&:hover]:[background-color:color-mix(in_oklch,_var(--primary)_88%,_var(--primary-foreground" +
"))] [:disabled+&]:opacity-50 [:disabled+&]:cursor-not-allowed [&[hidden]]:hidden data-[variant=link]:px-0" +
" data-[variant=link]:border-0 data-[variant=link]:border-current data-[variant=link]:border-none data-[va" +
"riant=link]:rounded-none data-[variant=link]:bg-transparent data-[variant=link]:text-primary data-[varian" +
"t=link]:underline data-[variant=link]:[text-underline-offset:0.2em] data-[variant=link]:active:px-0 data-" +
"[variant=link]:active:border-0 data-[variant=link]:active:border-current data-[variant=link]:active:borde" +
"r-none data-[variant=link]:active:rounded-none data-[variant=link]:active:bg-transparent data-[variant=li" +
"nk]:active:text-primary data-[variant=link]:active:underline data-[variant=link]:active:[text-underline-o" +
"ffset:0.2em] data-[variant=link]:[min-inline-size:0] [@media_(any-hover:_hover)_and_(any-pointer:_fine)]:" +
"data-[variant=link]:hover:px-0 [@media_(any-hover:_hover)_and_(any-pointer:_fine)]:data-[variant=link]:ho" +
"ver:border-0 [@media_(any-hover:_hover)_and_(any-pointer:_fine)]:data-[variant=link]:hover:border-current" +
" [@media_(any-hover:_hover)_and_(any-pointer:_fine)]:data-[variant=link]:hover:border-none [@media_(any-h" +
"over:_hover)_and_(any-pointer:_fine)]:data-[variant=link]:hover:rounded-none [@media_(any-hover:_hover)_a" +
"nd_(any-pointer:_fine)]:data-[variant=link]:hover:bg-transparent [@media_(any-hover:_hover)_and_(any-poin" +
"ter:_fine)]:data-[variant=link]:hover:text-primary [@media_(any-hover:_hover)_and_(any-pointer:_fine)]:da" +
"ta-[variant=link]:hover:underline [@media_(any-hover:_hover)_and_(any-pointer:_fine)]:data-[variant=link]" +
":hover:[text-underline-offset:0.2em] data-[variant=link]:before:content-none data-[variant=link]:after:co" +
"ntent-none data-[variant=link]:focus-visible:[outline:2px_solid_var(--ring)] data-[variant=link]:focus-vi" +
"sible:[outline-offset:3px] [[data-uiify-toggle]>input:focus-visible+&]:[box-shadow:0_0_0_var(--ring-width" +
")_color-mix(in_oklch,_var(--ring)_16%,_transparent)] [[data-uiify-toggle-group]_[data-part=item]>input:fo" +
"cus-visible+&]:[box-shadow:0_0_0_var(--ring-width)_color-mix(in_oklch,_var(--ring)_16%,_transparent)] [[d" +
"ata-uiify-modal][data-part=close]&]:border-transparent [[data-uiify-modal][data-part=close]&]:[background" +
"-color:color-mix(in_oklch,_var(--foreground)_8%,_transparent)] [[data-uiify-modal][data-part=close]&]:tex" +
"t-foreground [[data-uiify-modal][data-part=close]&:hover]:[background-color:color-mix(in_oklch,_var(--for" +
"eground)_14%,_transparent)] [[data-uiify-modal][data-part=close]&:active]:[background-color:color-mix(in_" +
"oklch,_var(--foreground)_20%,_transparent)] [[data-uiify-pagination]_span&:not([data-disabled])]:cursor-d" +
"efault [[data-uiify-input-group]>[data-part=addon]>&]:[min-inline-size:0] [[data-uiify-input-group]>[data" +
"-part=addon]>&]:[max-inline-size:100%] [[data-uiify-input-group]>[data-part=addon]>&]:whitespace-normal [" +
"[data-uiify-input-group]>[data-part=addon]>&]:[overflow-wrap:anywhere] [[data-uiify-item-content]>&]:[jus" +
"tify-self:start]"
}
>
Choose files
</FileUpload.Trigger>
<FileUpload.Dropzone className="">Drop files here</FileUpload.Dropzone>
<FileUpload.List className="">
{(files) =>
files.map((file, index) => (
<FileUpload.Item key={file.name} index={index} className="">
<span>
{file.name} · {(file.size / 1024).toFixed(1)} KB
</span>
<FileUpload.ItemRemove index={index} aria-label={"Remove " + file.name}>
×
</FileUpload.ItemRemove>
</FileUpload.Item>
))
}
</FileUpload.List>
</FileUpload.Root>
);
}/* app.css */
@layer reset, ui, app, tokens, base, demo, components, utilities, theme;
@import "@gunkin/uiify/styles/reset";
@import "@gunkin/uiify/components/behavior.css";
@import "@gunkin/uiify/styles/tokens";
@import "@gunkin/uiify/styles/tailwind";
@import "tailwindcss/utilities.css";
@theme { --spacing: 0.25rem; --font-weight-normal: 400; --font-weight-medium: 500; --font-weight-semibold: 600; --font-weight-bold: 700; }
@source inline('grid gap-md [inline-size:100%] [max-inline-size:24rem] [&_[data-part=dropzone]]:flex [&_[data-part=dropzone]]:items-center [&_[data-part=dropzone]]:justify-center [&_[data-part=dropzone]]:[min-block-size:8rem] [&_[data-part=dropzone]]:p-lg [&_[data-part=dropzone]]:[border-width:2px] [&_[data-part=dropzone]]:[border-style:dashed] [&_[data-part=dropzone]]:border-input [&_[data-part=dropzone]]:rounded-xl [&_[data-part=dropzone]]:[corner-shape:squircle] [&_[data-part=dropzone]]:bg-secondary [&_[data-part=dropzone]]:text-muted-foreground [&_[data-part=dropzone]]:text-sm [&_[data-part=dropzone]]:text-center [&_[data-part=dropzone]]:[transition:var(--transition-colors)] [&_[data-part=dropzone]:hover]:border-primary [&_[data-part=dropzone]:hover]:[background-color:color-mix(in_oklch,_var(--primary)_8%,_transparent)] [&_[data-part=dropzone]:hover]:text-foreground [&_[data-part=dropzone][data-dragging]]:border-primary [&_[data-part=dropzone][data-dragging]]:[background-color:color-mix(in_oklch,_var(--primary)_8%,_transparent)] [&_[data-part=dropzone][data-dragging]]:text-foreground [&_[data-part=dropzone][data-disabled]]:opacity-50 [&_[data-part=dropzone][data-disabled]]:cursor-not-allowed [&_[data-part=list]]:grid [&_[data-part=list]]:gap-xs [&_[data-part=list]]:m-0 [&_[data-part=list]]:p-0 [&_[data-part=list]]:list-none [&_[data-part=item]]:flex [&_[data-part=item]]:items-center [&_[data-part=item]]:gap-sm [&_[data-part=item]]:[padding:var(--space-sm)_var(--space-md)] [&_[data-part=item]]:border-[length:var(--border-width)] [&_[data-part=item]]:border-solid [&_[data-part=item]]:border-border [&_[data-part=item]]:rounded-lg [&_[data-part=item]]:[corner-shape:squircle] [&_[data-part=item]]:bg-card [&_[data-part=item]]:text-sm [&_[data-part=file-info]]:grid [&_[data-part=file-info]]:[gap:0.25rem] [&_[data-part=file-info]]:[min-inline-size:0] [&_[data-part=file-info]]:[flex:1] [&_[data-part=file-info]]:[overflow-wrap:anywhere] [&_small]:text-muted-foreground [&_[data-part=remove]]:grid [&_[data-part=remove]]:place-items-center [&_[data-part=remove]]:flex-none [&_[data-part=remove]]:[inline-size:2rem] [&_[data-part=remove]]:[block-size:2rem] [&_[data-part=remove]]:[margin-inline-start:auto] [&_[data-part=remove]]:border-0 [&_[data-part=remove]]:border-current [&_[data-part=remove]]:border-none [&_[data-part=remove]]:rounded-[50%] [&_[data-part=remove]]:bg-secondary [&_[data-part=remove]]:text-foreground [&_[data-part=remove]]:[font-size:1.25rem] [&_[data-part=remove]]:leading-none [&_[data-part=remove]]:cursor-pointer [&_[data-part=remove]:hover]:bg-accent [&_[data-part=remove]:focus-visible]:[outline:2px_solid_var(--ring)] [&_[data-part=remove]:focus-visible]:[outline-offset:2px] focus-visible:[outline:none] focus-visible:[box-shadow:0_0_0_var(--ring-width)_color-mix(in_oklch,_var(--ring)_16%,_transparent)] [--uiify-button-rim:rgb(255_255_255_/_20%)] [--uiify-button-wash:light-dark(rgb(255_255_255_/_15%),_rgb(255_255_255_/_30%))] relative isolate inline-flex items-center justify-center [gap:0.375rem] [min-block-size:44px] [min-inline-size:44px] py-0 [padding-inline:1.25rem] border-[length:var(--border-width)] border-solid border-transparent rounded-[var(--radius-full)] bg-foreground bg-clip-padding text-background text-base font-[number:var(--weight-medium)] leading-6 no-underline cursor-pointer [transition:var(--transition-colors)] before:absolute before:inset-0 before:z-[-1] before:[border-radius:inherit] before:pointer-events-none after:absolute after:inset-0 after:z-[-1] after:[border-radius:inherit] after:pointer-events-none before:[background-color:var(--uiify-button-wash)] before:[filter:blur(10px)] before:[translate:0_-50%] before:[clip-path:inset(50%_0_-50%_0_round_var(--radius-full))] after:[padding:0.5px] after:[background-image:linear-gradient(to_bottom,_var(--uiify-button-rim),_transparent_75%)] after:[mask:linear-gradient(#fff_0_0)_content-box_exclude,_linear-gradient(#fff_0_0)] [&:is([data-size=sm],[data-size=icon-sm])]:[min-block-size:32px] [&:is([data-size=sm],[data-size=icon-sm])]:[min-inline-size:32px] [&:is([data-size=sm],[data-size=icon-sm])]:[padding-inline:0.75rem] [&:is([data-size=sm],[data-size=icon-sm])]:[gap:0.25rem] [&:is([data-size=sm],[data-size=icon-sm])]:text-sm [&:is([data-size=sm],[data-size=icon-sm])]:leading-5 [&:is([data-size=lg],[data-size=icon-lg])]:[min-block-size:56px] [&:is([data-size=lg],[data-size=icon-lg])]:[min-inline-size:56px] [&:is([data-size=lg],[data-size=icon-lg])]:[padding-inline:1.75rem] [&:is([data-size=lg],[data-size=icon-lg])]:text-lg [&:is([data-size=lg],[data-size=icon-lg])]:leading-7 [&[data-size^=icon]]:px-0 [@media_(any-hover:_hover)_and_(any-pointer:_fine)]:hover:[&:not(:disabled,[data-disabled])]:[background-color:color-mix(in_oklch,_var(--foreground)_90%,_var(--background))] active:[&:not(:disabled,[data-disabled])]:[background-color:color-mix(in_oklch,_var(--foreground)_80%,_var(--background))] disabled:opacity-50 disabled:cursor-not-allowed data-[disabled]:opacity-50 data-[disabled]:cursor-not-allowed data-[variant=secondary]:[&:where(:not(:checked+*,[data-selected],[aria-pressed=true],[aria-current=page]))]:[--uiify-button-rim:light-dark(rgb(0_0_0_/_5%),_rgb(255_255_255_/_10%))] data-[variant=secondary]:[&:where(:not(:checked+*,[data-selected],[aria-pressed=true],[aria-current=page]))]:[--uiify-button-wash:light-dark(rgb(255_255_255_/_20%),_rgb(255_255_255_/_5%))] data-[variant=secondary]:[&:where(:not(:checked+*,[data-selected],[aria-pressed=true],[aria-current=page]))]:[background-color:color-mix(in_oklch,_var(--foreground)_10%,_transparent)] data-[variant=secondary]:[&:where(:not(:checked+*,[data-selected],[aria-pressed=true],[aria-current=page]))]:text-foreground data-[variant=outline]:[&:where(:not(:checked+*,[data-selected],[aria-pressed=true],[aria-current=page]))]:[--uiify-button-rim:color-mix(in_oklch,_var(--foreground)_25%,_transparent)] data-[variant=outline]:[&:where(:not(:checked+*,[data-selected],[aria-pressed=true],[aria-current=page]))]:[border-color:color-mix(in_oklch,_var(--foreground)_20%,_transparent)] data-[variant=outline]:[&:where(:not(:checked+*,[data-selected],[aria-pressed=true],[aria-current=page]))]:bg-transparent data-[variant=outline]:[&:where(:not(:checked+*,[data-selected],[aria-pressed=true],[aria-current=page]))]:text-foreground data-[variant=outline]:before:content-none data-[variant=outline]:after:[inset:calc(-1_*_var(--border-width))] data-[variant=outline]:after:[padding:var(--border-width)] data-[variant=outline]:[&:where(:not(:checked+*,[data-selected],[aria-pressed=true],[aria-current=page]))]:active:[&:not(:disabled,[data-disabled])]:[background-color:color-mix(in_oklch,_var(--foreground)_16%,_transparent)] data-[variant=ghost]:[&:where(:not(:checked+*,[data-selected],[aria-pressed=true],[aria-current=page]))]:bg-transparent data-[variant=ghost]:[&:where(:not(:checked+*,[data-selected],[aria-pressed=true],[aria-current=page]))]:text-foreground data-[variant=ghost]:before:content-none data-[variant=ghost]:after:content-none data-[variant=ghost]:[&:where(:not(:checked+*,[data-selected],[aria-pressed=true],[aria-current=page]))]:active:[&:not(:disabled,[data-disabled])]:[background-color:color-mix(in_oklch,_var(--foreground)_16%,_transparent)] data-[variant=secondary]:[&:where(:not(:checked+*,[data-selected],[aria-pressed=true],[aria-current=page]))]:active:[&:not(:disabled,[data-disabled])]:[background-color:color-mix(in_oklch,_var(--foreground)_26%,_transparent)] data-[variant=destructive]:[&:where(:not(:checked+*,[data-selected],[aria-pressed=true],[aria-current=page]))]:[--uiify-button-wash:rgb(255_255_255_/_15%)] data-[variant=destructive]:[&:where(:not(:checked+*,[data-selected],[aria-pressed=true],[aria-current=page]))]:bg-destructive data-[variant=destructive]:text-destructive-foreground data-[variant=destructive]:[&:where(:not(:checked+*,[data-selected],[aria-pressed=true],[aria-current=page]))]:active:[&:not(:disabled,[data-disabled])]:[background-color:color-mix(in_oklch,_var(--destructive),_black_20%)] [@media_(any-hover:_hover)_and_(any-pointer:_fine)]:data-[variant=outline]:[&:where(:not(:checked+*,[data-selected],[aria-pressed=true],[aria-current=page]))]:hover:[&:not(:disabled,[data-disabled])]:[background-color:color-mix(in_oklch,_var(--foreground)_10%,_transparent)] [@media_(any-hover:_hover)_and_(any-pointer:_fine)]:data-[variant=ghost]:[&:where(:not(:checked+*,[data-selected],[aria-pressed=true],[aria-current=page]))]:hover:[&:not(:disabled,[data-disabled])]:[background-color:color-mix(in_oklch,_var(--foreground)_10%,_transparent)] [@media_(any-hover:_hover)_and_(any-pointer:_fine)]:data-[variant=secondary]:[&:where(:not(:checked+*,[data-selected],[aria-pressed=true],[aria-current=page]))]:hover:[&:not(:disabled,[data-disabled])]:[background-color:color-mix(in_oklch,_var(--foreground)_20%,_transparent)] [@media_(any-hover:_hover)_and_(any-pointer:_fine)]:data-[variant=destructive]:[&:where(:not(:checked+*,[data-selected],[aria-pressed=true],[aria-current=page]))]:hover:[&:not(:disabled,[data-disabled])]:[background-color:color-mix(in_oklch,_var(--destructive),_black_12%)] [&:is([data-selected],[aria-pressed=true],[aria-current=page])]:[--uiify-button-rim:rgb(255_255_255_/_20%)] [&:is([data-selected],[aria-pressed=true],[aria-current=page])]:[--uiify-button-wash:rgb(255_255_255_/_15%)] [&:is([data-selected],[aria-pressed=true],[aria-current=page])]:border-transparent [&:is([data-selected],[aria-pressed=true],[aria-current=page])]:bg-primary [&:is([data-selected],[aria-pressed=true],[aria-current=page])]:text-primary-foreground [:checked+&]:[--uiify-button-rim:rgb(255_255_255_/_20%)] [:checked+&]:[--uiify-button-wash:rgb(255_255_255_/_15%)] [:checked+&]:border-transparent [:checked+&]:bg-primary [:checked+&]:text-primary-foreground [@media_(any-hover:_hover)_and_(any-pointer:_fine)]:[&:is([data-selected],[aria-pressed=true],[aria-current=page]):hover:not(:disabled,[data-disabled])]:[background-color:color-mix(in_oklch,_var(--primary)_88%,_var(--primary-foreground))] [@media_(any-hover:_hover)_and_(any-pointer:_fine)]:[:checked:not(:disabled)+&:hover]:[background-color:color-mix(in_oklch,_var(--primary)_88%,_var(--primary-foreground))] [:disabled+&]:opacity-50 [:disabled+&]:cursor-not-allowed [&[hidden]]:hidden data-[variant=link]:px-0 data-[variant=link]:border-0 data-[variant=link]:border-current data-[variant=link]:border-none data-[variant=link]:rounded-none data-[variant=link]:bg-transparent data-[variant=link]:text-primary data-[variant=link]:underline data-[variant=link]:[text-underline-offset:0.2em] data-[variant=link]:active:px-0 data-[variant=link]:active:border-0 data-[variant=link]:active:border-current data-[variant=link]:active:border-none data-[variant=link]:active:rounded-none data-[variant=link]:active:bg-transparent data-[variant=link]:active:text-primary data-[variant=link]:active:underline data-[variant=link]:active:[text-underline-offset:0.2em] data-[variant=link]:[min-inline-size:0] [@media_(any-hover:_hover)_and_(any-pointer:_fine)]:data-[variant=link]:hover:px-0 [@media_(any-hover:_hover)_and_(any-pointer:_fine)]:data-[variant=link]:hover:border-0 [@media_(any-hover:_hover)_and_(any-pointer:_fine)]:data-[variant=link]:hover:border-current [@media_(any-hover:_hover)_and_(any-pointer:_fine)]:data-[variant=link]:hover:border-none [@media_(any-hover:_hover)_and_(any-pointer:_fine)]:data-[variant=link]:hover:rounded-none [@media_(any-hover:_hover)_and_(any-pointer:_fine)]:data-[variant=link]:hover:bg-transparent [@media_(any-hover:_hover)_and_(any-pointer:_fine)]:data-[variant=link]:hover:text-primary [@media_(any-hover:_hover)_and_(any-pointer:_fine)]:data-[variant=link]:hover:underline [@media_(any-hover:_hover)_and_(any-pointer:_fine)]:data-[variant=link]:hover:[text-underline-offset:0.2em] data-[variant=link]:before:content-none data-[variant=link]:after:content-none data-[variant=link]:focus-visible:[outline:2px_solid_var(--ring)] data-[variant=link]:focus-visible:[outline-offset:3px] [[data-uiify-toggle]>input:focus-visible+&]:[box-shadow:0_0_0_var(--ring-width)_color-mix(in_oklch,_var(--ring)_16%,_transparent)] [[data-uiify-toggle-group]_[data-part=item]>input:focus-visible+&]:[box-shadow:0_0_0_var(--ring-width)_color-mix(in_oklch,_var(--ring)_16%,_transparent)] [[data-uiify-modal][data-part=close]&]:border-transparent [[data-uiify-modal][data-part=close]&]:[background-color:color-mix(in_oklch,_var(--foreground)_8%,_transparent)] [[data-uiify-modal][data-part=close]&]:text-foreground [[data-uiify-modal][data-part=close]&:hover]:[background-color:color-mix(in_oklch,_var(--foreground)_14%,_transparent)] [[data-uiify-modal][data-part=close]&:active]:[background-color:color-mix(in_oklch,_var(--foreground)_20%,_transparent)] [[data-uiify-pagination]_span&:not([data-disabled])]:cursor-default [[data-uiify-input-group]>[data-part=addon]>&]:[min-inline-size:0] [[data-uiify-input-group]>[data-part=addon]>&]:[max-inline-size:100%] [[data-uiify-input-group]>[data-part=addon]>&]:whitespace-normal [[data-uiify-input-group]>[data-part=addon]>&]:[overflow-wrap:anywhere] [[data-uiify-item-content]>&]:[justify-self:start]');
@source inline("before:content-[''] after:content-['']");
@layer base { :root { --uiify-control-text: var(--text-sm); } @media (pointer: coarse) { :root { --uiify-control-text: var(--text-base); } } }
/* Authored animation and registered properties used by the Tailwind skin.
* Visual rules belong in utility recipes; only at-rule definitions live here. */
@layer components {
@keyframes uiify-presence-in {
from {
opacity: 0;
scale: var(--presence-scale);
}
}
}
@layer components {
@keyframes uiify-presence-out {
to {
opacity: 0;
scale: var(--presence-scale);
}
}
}
@property --uiify-spinner-tail {
syntax: "<angle>";
inherits: false;
initial-value: 0deg;
}
@property --uiify-spinner-length {
syntax: "<angle>";
inherits: false;
initial-value: 36deg;
}
@layer components {
@keyframes uiify-spinner-rotate {
to {
transform: rotate(360deg);
}
}
}
@layer components {
@keyframes uiify-spinner-orbit {
0% {
--uiify-spinner-tail: 0deg;
--uiify-spinner-length: 36deg;
}
50% {
--uiify-spinner-tail: 90deg;
--uiify-spinner-length: 324deg;
}
100% {
--uiify-spinner-tail: 360deg;
--uiify-spinner-length: 36deg;
}
}
}
@layer components {
@keyframes uiify-skeleton-pulse {
50% {
opacity: 0.45;
}
}
}
@layer components {
@keyframes uiify-skeleton-shimmer {
to {
background-position: -200% 0;
}
}
}
@layer components {
@keyframes uiify-skeleton-reveal {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
}
@property --candle-body-height {
syntax: "<percentage>";
inherits: true;
initial-value: 0%;
}
@property --candle-body-top {
syntax: "<percentage>";
inherits: true;
initial-value: 50%;
}
@property --candle-slot {
syntax: "<percentage>";
inherits: true;
initial-value: 100%;
}
@property --candle-wick-height {
syntax: "<percentage>";
inherits: true;
initial-value: 0%;
}
@property --candle-wick-top {
syntax: "<percentage>";
inherits: true;
initial-value: 50%;
}
@property --candle-x {
syntax: "<percentage>";
inherits: true;
initial-value: 0%;
}
@property --candle-scale {
syntax: "<number>";
inherits: true;
initial-value: 1;
}
@property --line-y {
syntax: "<percentage>";
inherits: true;
initial-value: 50%;
}
@property --line-prev-y {
syntax: "<percentage>";
inherits: true;
initial-value: 50%;
}
@property --line-count {
syntax: "<integer>";
inherits: false;
initial-value: 1;
}
@property --line-scale {
syntax: "<number>";
inherits: true;
initial-value: 1;
}
@layer components {
@keyframes uiify-otp-input-caret-blink {
50% {
opacity: 0;
}
}
}API
FileUpload.Root
| Prop | Type | Default | Description |
|---|---|---|---|
children | ReactNode | — | File upload parts: hidden input, trigger, dropzone and list. |
value | readonly File[] | — | Current files, making the component controlled. |
defaultValue | readonly File[] | [] | Starting files for the uncontrolled component. |
onValueChange | (files: File[]) => void | — | Called with the accepted files whenever the selection changes. |
onReject | (rejections: FileRejection[]) => void | — | Called with the files that failed validation and why. |
accept | string[] | [] | Accepted file types, as extensions or MIME types. |
maxFiles | number | — | Largest number of files that can be held at once. |
maxSize | number | — | Largest accepted file size in bytes. |
minSize | number | — | Smallest accepted file size in bytes. |
multiple | boolean | false | Lets the picker select more than one file. |
disabled | boolean | false | Disables the picker, the dropzone and the remove buttons. |
FileUpload.HiddenInput
| Prop | Type | Default | Description |
|---|---|---|---|
className | string | — | Class applied to the native file input. |
FileUpload.Trigger
| Prop | Type | Default | Description |
|---|---|---|---|
children | ReactNode | — | Content of the control that opens the file picker. |
className | string | — | Class applied to the trigger element. |
render | ReactElement | ((props, state) => ReactElement) | — | Replace the rendered element while keeping behavior and accessibility wiring. |
FileUpload.Dropzone
| Prop | Type | Default | Description |
|---|---|---|---|
children | ReactNode | — | Content of the drop target. |
className | string | — | Class applied to the dropzone element. |
render | ReactElement | ((props, state) => ReactElement) | — | Replace the rendered element while keeping behavior and accessibility wiring. |
FileUpload.List
| Prop | Type | Default | Description |
|---|---|---|---|
children | ReactNode | ((files: readonly File[]) => ReactNode) | — | Render accepted files from the live list. Controlled value changes and external resets are reflected immediately. |
className | string | — | Class applied to the list element. |
FileUpload.Item
| Prop | Type | Default | Description |
|---|---|---|---|
children | ReactNode | — | Content describing the file, such as its name and size. |
className | string | — | Class applied to the item element. |
index* | number | — | Position of the file this item represents. |
render | ReactElement | ((props, state) => ReactElement) | — | Replace the rendered element while keeping behavior and accessibility wiring. |
FileUpload.ItemRemove
| Prop | Type | Default | Description |
|---|---|---|---|
className | string | — | Class applied to the remove button. |
index* | number | — | Position of the file this button removes. |
FileRejection
| Prop | Type | Default | Description |
|---|---|---|---|
file* | File | — | The file that was not accepted. |
reason* | "accept" | "max-size" | "max-files" | — | Which rule the file failed. |
Keyboard
| Key | Action |
|---|---|
| Enter / Space | Open the file picker from Trigger or remove a focused file |
| Tab | Move between the picker trigger and file removal controls |
Accessibility
Give HiddenInput an accessible label. List receives the accepted files and renders their names, sizes and labeled removal buttons. Root supports controlled value, cumulative maxFiles, maxSize and accept validation; onReject reports rejected files. Disabled roots block picker, drop and removal actions. File handling requires hydration.
Styling hooks
data-uiify-file-uploaddata-partdata-disableddata-state