Components · Data Entry
Upload
Select files by clicking a drop zone or dragging onto it — then manage them as rows or picture tiles with per-item state.
Examples
List type
State
Uploader · drop zone + file rows · uploading / done / error states · remove ✕
No files selected
<!-- Paste-and-run: save as .html and open in a browser. No build step.
<aha-uploader> is the SAME shared custom element React and Vue consume.
Click or drag files onto the zone; it emits a composed `change` event with the File[].
Feed `items` to render managed file rows/tiles with per-item state + a remove ✕. -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/ahaslides-product/ahaslides-design@master/lib/tokens.css">
<script type="module">
import 'https://cdn.jsdelivr.net/gh/ahaslides-product/ahaslides-design@master/lib/icons.js'; // <aha-icon> glyphs
import 'https://cdn.jsdelivr.net/gh/ahaslides-product/ahaslides-design@master/lib/aha-uploader.js'; // <aha-uploader>
</script>
<!-- text mode: a drop zone + managed file rows (done / uploading / error) -->
<aha-uploader id="up" accept="image/*" multiple
items='[{"name":"cover.png","status":"done"},{"name":"clip.mov","status":"uploading","percent":40},{"name":"bad.zip","status":"error"}]'>
</aha-uploader>
<!-- picture-card mode: thumbnail tiles + a dashed add-box; cap with maxcount -->
<aha-uploader listtype="picture-card" maxcount="4"
items='[{"name":"a.png","status":"done","thumb":"a.png"}]'>
</aha-uploader>
<script>
const up = document.getElementById('up');
up.addEventListener('change', (e) => console.log('files:', e.detail.files.map((f) => f.name)));
up.addEventListener('remove', (e) => console.log('remove:', e.detail.name, e.detail.index));
</script>
import '@ahaslides-product/design/aha-uploader'; // registers <aha-uploader>
import '@ahaslides-product/design/icons'; // registers <aha-icon> (glyphs)
import { useRef, useEffect } from 'react';
// React 18 needs a thin wrapper (composed CustomEvents + JSON attrs); React 19 can use the tag directly.
function AhaUploader({ multiple, accept, disabled, listType, maxCount, items, onFiles, onRemove }) {
const ref = useRef();
useEffect(() => {
const el = ref.current;
const f = (e) => onFiles?.(e.detail.files); // composed CustomEvent, File[]
const r = (e) => onRemove?.(e.detail.name, e.detail.index);
el.addEventListener('change', f);
el.addEventListener('remove', r);
return () => { el.removeEventListener('change', f); el.removeEventListener('remove', r); };
});
return (
<aha-uploader
ref={ref}
multiple={multiple || undefined}
accept={accept}
disabled={disabled || undefined}
listtype={listType} // "text" | "picture-card"
maxcount={maxCount}
items={items ? JSON.stringify(items) : undefined} // [{ name, status?, percent?, thumb? }]
/>
);
}
// usage — a drop zone with managed rows (done / uploading / error) + a remove handler
<AhaUploader
accept="image/*"
multiple
items={[
{ name: 'cover.png', status: 'done' },
{ name: 'clip.mov', status: 'uploading', percent: 40 },
{ name: 'bad.zip', status: 'error' },
]}
onFiles={(files) => setFiles(files)}
onRemove={(name) => dropFile(name)}
/>
// main.ts — register the element + mark aha-* as custom elements
import '@ahaslides-product/design/aha-uploader';
import '@ahaslides-product/design/icons'; // <aha-icon> glyphs
app.config.compilerOptions.isCustomElement = (tag) => tag.startsWith('aha-');
// Component.vue — text mode: a drop zone + managed file rows (done / uploading / error)
<template>
<aha-uploader
accept="image/*"
multiple
listtype="text"
:items="JSON.stringify(items)" // [{ name, status?, percent?, thumb? }]
@change="onFiles($event.detail.files)"
@remove="onRemove($event.detail.name, $event.detail.index)"
/>
<!-- picture-card mode: thumbnail tiles + a dashed add-box, capped at 4 -->
<aha-uploader listtype="picture-card" maxcount="4" :items="JSON.stringify(tiles)" />
</template>
<script setup>
const items = [
{ name: 'cover.png', status: 'done' },
{ name: 'clip.mov', status: 'uploading', percent: 40 },
{ name: 'bad.zip', status: 'error' },
];
const tiles = [{ name: 'a.png', status: 'done', thumb: 'a.png' }];
</script>
API
| Prop | Type | Default | Notes |
|---|---|---|---|
listtype | 'text' | 'picture-card' | text | Drop zone + file rows, or a grid of thumbnail tiles with a dashed add-box |
items | JSON list | [] | Managed files: [{ name, status?: 'normal'|'uploading'|'error'|'done', percent?, thumb? }] |
multiple | boolean | false | Allow selecting more than one file |
accept | string | — | MIME/type filter, e.g. "image/*" |
maxcount | number | ∞ | Cap; the add trigger hides once items reach it |
disabled | boolean | false | Dims and disables the zone / add-box |
change | CustomEvent<{files}> | — | Composed event on select; read e.detail.files (File[]) |
remove | CustomEvent<{name,index}> | — | Composed event when an item's ✕ is clicked |
Install
# .npmrc — once: point the @ahaslides-product scope at GitHub Packages
@ahaslides-product:registry=https://npm.pkg.github.com
//npm.pkg.github.com/:_authToken=${GITHUB_TOKEN} # a GitHub token with read:packages
npm i @ahaslides-product/design
import '@ahaslides-product/design/tokens.css'; // once, at the app root
import '@ahaslides-product/design/aha-uploader'; // registers <aha-uploader>
Agent feed for this component (absolute, fetchable anywhere): uploader.agent.json · uploader.md · uploader.llms.txt
When to use
When to use
- Upload — the user brings a file in — an image, a CSV, a document
- Input — capturing typed text, not a file
- Colour picker — choosing a value from presets rather than importing
State what you accept in the zone copy and set `accept` to match. Drive `items[].status` from your upload lifecycle (uploading → done/error) and update `percent` as bytes land. Show the error reason near the failed row. Use `picture-card` for images, `text` for documents. Offer both click and drag — both are wired here.
Surfaces
editor dashboard settings
Spec
Zone 1px dashed border-strong #D4D4D4, radius 8, pad 24 16 · Rest text text-tertiary #8A8A8A, 14/21 · Hover / drag border + accent tint color-primary / bg-accent (motion-mid, persistent zone) · File row 1px border #E3E3E3, radius 8, file icon + name + remove ✕ · Uploading 4px pill progress track (split), color-primary fill, width animates (motion-slow) · Error item border + text color-error #F5222D on bg-negative, warning glyph · Done item check-circle glyph color-success #16C49A · Picture-card 104px tiles + dashed add-box; add-box is the trigger