Build / Molecules
ImageUpload
An image field: what it is, what it must be, what is there now, and how to change it.
The preview is shown at the shape the image will actually be cropped to, because a square thumbnail of something that ships as a wide banner tells you nothing about what you just uploaded.
Built on a real <input type="file"> driven by a <label>, so the picker
opens from the keyboard and the control is announced as a file input rather
than a button that mysteriously opens a dialog. The input keeps its own
accessible name even while the styled button is what you see.
Deck has no upload pipeline — this hands you the File and gets out of the
way. Rendering the result is the caller's job.
import { ImageUpload } from '@meetcard/deck'Example
<ImageUpload label="Profile photo" shape="circle" src={photo} description="Square, at least 400×400px. JPG or PNG." onFileSelect={setFile} />Stories
Nothing uploaded yet. The constraints are stated before the picker opens.
Source
const Empty = () => <Card style={{ maxWidth: 480 }}>
<ImageUpload
label="Profile photo"
description="Square, at least 400×400px. JPG or PNG."
shape="circle" />
</Card>;With an image, previewed at the crop it ships at, with replace and remove.
Source
const WithImage = () => <Card style={{ maxWidth: 480 }}>
<ImageUpload
label="Profile photo"
description="Square, at least 400×400px. JPG or PNG."
shape="circle"
src="https://images.unsplash.com/photo-1633332755192-727a05c4013d?w=256&h=256&fit=crop"
onRemove={() => {}} />
</Card>;The three crops, side by side. The preview wears the shape the image will actually be used at — a person's photo, a company mark, a wide banner.
Source
const Shapes = () => (
<Stack gap={24} style={{ maxWidth: 480 }}>
<ImageUpload
label="Profile photo"
shape="circle"
src={PHOTO}
description="Square, at least 400×400px."
/>
<ImageUpload
label="Logo"
shape="square"
description="Full-colour mark for light backgrounds. Square PNG or SVG."
/>
<ImageUpload
label="Cover image"
shape="cover"
description="4:3 image (1600×1200 recommended)."
/>
</Stack>
);Choosing a file names it back, since a picker confirms nothing itself.
Source
const ReportsTheChosenFile = () => {
const [name, setName] = useState<string | null>(null)
return (
<Card style={{ maxWidth: 480 }}>
<ImageUpload
label="Profile photo"
description="Square, at least 400×400px. JPG or PNG."
shape="circle"
onFileSelect={(file) => setName(file?.name ?? null)} />
<Text size="xs" style={{ marginTop: 12 }}>Caller received: {name ?? '—'}</Text>
</Card>
);
};Disabled — the image is shown but cannot be changed here.
Source
const Disabled = () => <Card style={{ maxWidth: 480 }}>
<ImageUpload
label="Profile photo"
description="Square, at least 400×400px. JPG or PNG."
shape="circle"
src="https://images.unsplash.com/photo-1633332755192-727a05c4013d?w=256&h=256&fit=crop"
disabled />
</Card>;Props
| Prop | Type | Default | Description |
|---|---|---|---|
labelrequired | ReactNode | — | What is being uploaded — "Profile photo", "Logo", "Cover image". |
accept | string | 'image/png,image/jpeg,image/webp,image/svg+xml' | File types offered in the picker. Defaults to common web images. |
actionLabel | string | — | The button's words. Defaults to "Replace" once there is an image. |
className | string | — | — |
description | ReactNode | — | The constraints, in the reader's terms — "Square, at least 400×400px. JPG or PNG." Say them before the upload, not in an error afterwards. |
disabled | boolean | — | — |
onFileSelect | (file: File | null) => void | — | The chosen file, or null when the picker is dismissed. |
onRemove | () => void | — | Renders a remove control. Omit when the image is not optional. |
shape | 'circle' | 'square' | 'cover' | 'square' | circle for a person, square for a logo, cover for a wide banner.
The preview's shape is the clearest statement of how the image is
cropped, so it is shown at the aspect it will actually be used. |
src | string | — | The current image, if there is one. |