<script setup>
import { Attachment } from "@dicehub/kappa/components/attachment";
const images = ["pressure.png", "velocity.png", "vorticity.png"];
</script>
<template>
<div class="attachments">
<Attachment.Group class="attachment-images" role="group" aria-label="Contour image attachments">
<Attachment.Root
v-for="image in images"
:key="image"
state="done"
orientation="vertical"
>
<Attachment.Media variant="image">
<img :src="`/${image}`" :alt="`${image} contour preview`" />
</Attachment.Media>
<Attachment.Content>
<Attachment.Title>{{ image }}</Attachment.Title>
<Attachment.Description>PNG · 1.8 MB</Attachment.Description>
</Attachment.Content>
</Attachment.Root>
</Attachment.Group>
<Attachment.Root class="attachment-row" state="uploading">
<Attachment.Media variant="icon" aria-hidden="true">FILE</Attachment.Media>
<Attachment.Content>
<Attachment.Title>volume-mesh.msh</Attachment.Title>
<Attachment.Description>Uploading · 64%</Attachment.Description>
</Attachment.Content>
</Attachment.Root>
<Attachment.Root class="attachment-row" state="done">
<Attachment.Media variant="icon" aria-hidden="true">FILE</Attachment.Media>
<Attachment.Content>
<Attachment.Title>solver-log.txt</Attachment.Title>
<Attachment.Description>Text · 184 KB</Attachment.Description>
</Attachment.Content>
</Attachment.Root>
</div>
</template>
<style scoped>
.attachments {
display: grid;
inline-size: min(100%, 24rem);
gap: 0.75rem;
}
.attachment-images,
.attachment-row {
inline-size: 100%;
}
</style>Installation
Barrel
import {
Attachment,
AttachmentRoot,
AttachmentMedia,
AttachmentContent,
AttachmentTitle,
AttachmentDescription,
AttachmentActions,
AttachmentAction,
AttachmentTrigger,
AttachmentGroup,
} from "@dicehub/kappa";Granular
import {
Attachment,
AttachmentRoot,
AttachmentMedia,
AttachmentContent,
AttachmentTitle,
AttachmentDescription,
AttachmentActions,
AttachmentAction,
AttachmentTrigger,
AttachmentGroup,
} from "@dicehub/kappa/components/attachment";Usage
Compose media, content, and actions inside the root. The attachment presents file state; file selection, upload transport, progress, retries, and removal remain consumer-owned.
<script setup>
import { Attachment } from "@dicehub/kappa/components/attachment";
</script>
<template>
<Attachment.Root state="done">
<Attachment.Media variant="icon">
<svg viewBox="0 0 16 16" aria-hidden="true">
<path d="M4 2h5l3 3v9H4V2Zm5 0v3h3" />
</svg>
</Attachment.Media>
<Attachment.Content>
<Attachment.Title>rotor-case.cas</Attachment.Title>
<Attachment.Description>Fluent case · 24.6 MB</Attachment.Description>
</Attachment.Content>
<Attachment.Actions>
<Attachment.Action aria-label="Remove rotor-case.cas">
<span aria-hidden="true">×</span>
</Attachment.Action>
</Attachment.Actions>
</Attachment.Root>
</template>Composition
Attachment.Group
└── Attachment.Root
├── Attachment.Media
├── Attachment.Content
│ ├── Attachment.Title
│ └── Attachment.Description
├── Attachment.Actions
│ └── Attachment.Action
└── Attachment.TriggerThe compound API and named exports are equivalent. Use named parts when explicit imports make a shared file-row composition easier to inspect.
<script setup>
import {
AttachmentRoot,
AttachmentMedia,
AttachmentContent,
AttachmentTitle,
AttachmentDescription,
AttachmentActions,
AttachmentAction,
} from "@dicehub/kappa/components/attachment";
</script>
<template>
<AttachmentRoot state="done">
<AttachmentMedia variant="icon" aria-hidden="true">CSV</AttachmentMedia>
<AttachmentContent>
<AttachmentTitle>forces.csv</AttachmentTitle>
<AttachmentDescription>CSV · 86 KB</AttachmentDescription>
</AttachmentContent>
<AttachmentActions>
<AttachmentAction aria-label="Remove forces.csv">×</AttachmentAction>
</AttachmentActions>
</AttachmentRoot>
</template>Examples
Icons and Images
Use variant="icon" for file-type symbols and variant="image" for meaningful previews. Keep decorative icons hidden and give preview images useful alt text.
<script setup>
import { Attachment } from "@dicehub/kappa/components/attachment";
const images = ["pressure.png", "velocity.png", "vorticity.png"];
</script>
<template>
<Attachment.Group class="attachment-images" role="group" aria-label="Contour image attachments">
<Attachment.Root
v-for="image in images"
:key="image"
state="done"
orientation="vertical"
>
<Attachment.Media variant="image">
<img :src="`/${image}`" :alt="`${image} contour preview`" />
</Attachment.Media>
<Attachment.Content>
<Attachment.Title>{{ image }}</Attachment.Title>
<Attachment.Description>PNG · 1.8 MB</Attachment.Description>
</Attachment.Content>
</Attachment.Root>
</Attachment.Group>
</template>
<style scoped>
.attachment-images {
inline-size: min(100%, 24rem);
}
</style>States
Set state from the real upload lifecycle. State controls visual treatment only; it does not add live-region semantics or perform an upload.
<script setup>
import { Attachment } from "@dicehub/kappa/components/attachment";
const files = [
{ state: "idle", name: "geometry.step", detail: "Ready to upload" },
{ state: "uploading", name: "volume-mesh.msh", detail: "Uploading · 64%" },
{ state: "processing", name: "run-042.foam", detail: "Indexing result fields" },
{ state: "error", name: "boundary-map.json", detail: "Upload failed. Try again." },
{ state: "done", name: "solver-log.txt", detail: "Text · 184 KB" },
];
</script>
<template>
<Attachment.Root class="attachment-row" v-for="file in files" :key="file.name" :state="file.state">
<Attachment.Media variant="icon" aria-hidden="true">FILE</Attachment.Media>
<Attachment.Content>
<Attachment.Title>{{ file.name }}</Attachment.Title>
<Attachment.Description>{{ file.detail }}</Attachment.Description>
</Attachment.Content>
</Attachment.Root>
</template>
<style scoped>
.attachment-row {
inline-size: min(100%, 24rem);
}
</style>Sizes
Default density suits review panels. Use sm or xs in compact lists, tables, and upload queues while retaining a readable file label.
<script setup>
import { Attachment } from "@dicehub/kappa/components/attachment";
</script>
<template>
<Attachment.Root class="attachment-row" v-for="size in ['default', 'sm', 'xs']" :key="size" :size="size" state="done">
<Attachment.Media variant="icon" aria-hidden="true">CSV</Attachment.Media>
<Attachment.Content>
<Attachment.Title>{{ size }} · residuals.csv</Attachment.Title>
<Attachment.Description>CSV · 96 KB</Attachment.Description>
</Attachment.Content>
</Attachment.Root>
</template>
<style scoped>
.attachment-row {
inline-size: min(100%, 24rem);
}
</style>Orientation
Horizontal attachments prioritize dense metadata. Vertical attachments give image previews more room and work well inside a group. Root is content-sized by default; set a logicalinline-size when a row or tile should use a specific width.
<script setup>
import { Attachment } from "@dicehub/kappa/components/attachment";
</script>
<template>
<Attachment.Root state="done" orientation="horizontal">
<Attachment.Media variant="icon" aria-hidden="true">CASE</Attachment.Media>
<Attachment.Content>
<Attachment.Title>horizontal.foam</Attachment.Title>
<Attachment.Description>OpenFOAM case</Attachment.Description>
</Attachment.Content>
</Attachment.Root>
<Attachment.Root class="attachment-tile" state="done" orientation="vertical">
<Attachment.Media variant="image">
<img src="/velocity-contour.png" alt="Velocity contour preview" />
</Attachment.Media>
<Attachment.Content>
<Attachment.Title>velocity.png</Attachment.Title>
<Attachment.Description>PNG · 2.1 MB</Attachment.Description>
</Attachment.Content>
</Attachment.Root>
</template>
<style scoped>
.attachment-tile {
inline-size: 7.5rem;
}
</style>Attachment Group
Group provides a horizontal snap-scrolling row. Add role="group", an accessible label, and tabindex="0" when keyboard users need to focus and scroll the collection.
<script setup>
import { Attachment } from "@dicehub/kappa/components/attachment";
const files = ["rotor-surface.stl", "pressure-field.vtk", "solver-log.txt", "residuals.csv"];
</script>
<template>
<Attachment.Group role="group" aria-label="Simulation attachments" tabindex="0">
<Attachment.Root class="attachment-row" v-for="file in files" :key="file" state="done">
<Attachment.Media variant="icon" aria-hidden="true">FILE</Attachment.Media>
<Attachment.Content>
<Attachment.Title>{{ file }}</Attachment.Title>
<Attachment.Description>Simulation file</Attachment.Description>
</Attachment.Content>
</Attachment.Root>
</Attachment.Group>
</template>
<style scoped>
.attachment-row {
inline-size: 16rem;
}
</style>Full-card Trigger
Trigger creates a full-card hit area as a native button or anchor. Actions remain above the trigger, so removing a file does not also open it. Label both controls by purpose and file name.
<script setup>
import { Attachment } from "@dicehub/kappa/components/attachment";
function removeFile() {
// Remove only this attachment.
}
</script>
<template>
<Attachment.Root state="done">
<Attachment.Media variant="icon" aria-hidden="true">CASE</Attachment.Media>
<Attachment.Content>
<Attachment.Title>rotor-case.cas</Attachment.Title>
<Attachment.Description>Fluent case · 24.6 MB</Attachment.Description>
</Attachment.Content>
<Attachment.Actions>
<Attachment.Action aria-label="Remove rotor-case.cas" @click="removeFile">×</Attachment.Action>
</Attachment.Actions>
<Attachment.Trigger
as="a"
href="/cases/rotor-case"
aria-label="Open rotor-case.cas"
/>
</Attachment.Root>
</template>Right-to-left
Logical alignment keeps media, content, and actions in the expected order in RTL layouts.
<script setup>
import { Attachment } from "@dicehub/kappa/components/attachment";
</script>
<template>
<div dir="rtl">
<Attachment.Root state="done">
<Attachment.Media variant="icon" aria-hidden="true">CSV</Attachment.Media>
<Attachment.Content>
<Attachment.Title>نتائج-الضغط.csv</Attachment.Title>
<Attachment.Description>CSV · ١٢٨ كيلوبايت</Attachment.Description>
</Attachment.Content>
<Attachment.Actions>
<Attachment.Action aria-label="إزالة نتائج الضغط">×</Attachment.Action>
</Attachment.Actions>
</Attachment.Root>
</div>
</template>Accessibility
- Attachment is presentational by default. Upload states do not implicitly add
role="status",role="alert", oraria-live. - Announce meaningful asynchronous progress deliberately. Reserve
role="alert"for errors requiring immediate attention. - Write visible error text that explains what failed and the available recovery; color and state styling are supplementary.
- Give preview images descriptive alt text. Use an empty alt for a redundant thumbnail and hide decorative SVG icons.
- Every icon-only Action and Trigger needs an accessible label that includes the operation and file name.
- Actions are independent native buttons. A full-card Trigger must not obscure them or duplicate their click behavior.
- Give scrollable groups
role="group"and a label, then make them focusable when keyboard scrolling is required. Preserve visible focus while the group scrolls. - State shimmer and visual transitions honor
prefers-reduced-motion.
API Reference
Attachment.Root
Renders a div and forwards native attributes and listeners.
| Prop | Type | Default | Description |
|---|---|---|---|
state | "idle" | "uploading" | "processing" | "error" | "done" | "done" | Reflects the consumer-managed file lifecycle in styling and data-state. |
size | "default" | "sm" | "xs" | "default" | Sets the attachment density and composed action size. |
orientation | "horizontal" | "vertical" | "horizontal" | Arranges media, content, and actions along the selected axis. |
Attachment.Media
| Prop | Type | Default | Description |
|---|---|---|---|
variant | "icon" | "image" | "icon" | Selects icon framing or edge-to-edge image treatment. |
Attachment.Action
Renders a compact, icon-only native button. Provide an accessible label containing the operation and file name.
| Prop | Type | Default | Description |
|---|---|---|---|
disabled | boolean | false | Disables the native icon-only action button. |
type | "button" | "submit" | "reset" | "button" | Sets the native button type. |
Attachment.Trigger
| Prop | Type | Default | Description |
|---|---|---|---|
as | "button" | "a" | "button" | Renders the full-card trigger as a native button or anchor. |
type | "button" | "submit" | "reset" | "button" | Sets the native button type; omitted when the trigger renders as an anchor. |
Parts
| Part | Element | Description |
|---|---|---|
Attachment.Content | div | Flexible wrapper for the title and supporting metadata. |
Attachment.Title | span | Single-line file name or primary label. |
Attachment.Description | span | Supporting metadata, progress, or error text. |
Attachment.Actions | div | Stacking boundary that keeps controls above a full-card trigger. |
Attachment.Action | button | Native button with type=button by default. |
Attachment.Group | div | Horizontally scrollable, snap-aligned collection. |
Exports
| Export | Description |
|---|---|
Attachment | Compound API exposing Root, Media, Content, Title, Description, Actions, Action, Trigger, and Group. |
AttachmentRoot | Unaugmented attachment root. |
AttachmentMedia | Icon or image media frame. |
AttachmentContent | File-information wrapper. |
AttachmentTitle | Primary file label. |
AttachmentDescription | Metadata, progress, or error copy. |
AttachmentActions | Container for independent attachment actions. |
AttachmentAction | Native button action. |
AttachmentTrigger | Full-card button or anchor trigger. |
AttachmentGroup | Scrollable attachment collection. |
ATTACHMENT_STATES | Readonly list of supported lifecycle states. |
ATTACHMENT_SIZES | Readonly list of supported densities. |
ATTACHMENT_ORIENTATIONS | Readonly list of supported orientations. |
ATTACHMENT_MEDIA_VARIANTS | Readonly list of media treatments. |
ATTACHMENT_BUTTON_TYPES | Readonly list of native button types. |
ATTACHMENT_TRIGGER_ELEMENTS | Readonly list of supported trigger elements. |
ATTACHMENT_DEFAULT_STATE | Default lifecycle state: done. |
ATTACHMENT_DEFAULT_SIZE | Default density: default. |
ATTACHMENT_DEFAULT_ORIENTATION | Default orientation: horizontal. |
ATTACHMENT_MEDIA_DEFAULT_VARIANT | Default media treatment: icon. |
ATTACHMENT_ACTION_DEFAULT_TYPE | Default native action type: button. |
ATTACHMENT_TRIGGER_DEFAULT_ELEMENT | Default trigger element: button. |
ATTACHMENT_TRIGGER_DEFAULT_TYPE | Default native trigger type: button. |
isAttachmentState | Runtime state type guard. |
isAttachmentSize | Runtime size type guard. |
isAttachmentOrientation | Runtime orientation type guard. |
isAttachmentMediaVariant | Runtime media-variant type guard. |
isAttachmentButtonType | Runtime native-button-type guard. |
isAttachmentTriggerElement | Runtime trigger-element guard. |
resolveAttachmentState | Resolves unknown input to a supported state. |
resolveAttachmentSize | Resolves unknown input to a supported size. |
resolveAttachmentOrientation | Resolves unknown input to a supported orientation. |
resolveAttachmentMediaVariant | Resolves unknown input to a supported media variant. |
resolveAttachmentButtonType | Resolves unknown input to a native button type. |
resolveAttachmentTriggerElement | Resolves unknown input to a trigger element. |
AttachmentProps | Alias of the public root props. |
AttachmentRootProps | Public root props. |
AttachmentMediaProps | Public media props. |
AttachmentActionProps | Public action props. |
AttachmentTriggerProps | Public trigger props. |
AttachmentSlots | Alias of the public root slot contract. |
AttachmentRootSlots | Root default-slot contract. |
AttachmentMediaSlots | Media default-slot contract. |
AttachmentContentSlots | Content default-slot contract. |
AttachmentTitleSlots | Title default-slot contract. |
AttachmentDescriptionSlots | Description default-slot contract. |
AttachmentActionsSlots | Actions default-slot contract. |
AttachmentActionSlots | Action default-slot contract. |
AttachmentTriggerSlots | Trigger default-slot contract. |
AttachmentGroupSlots | Group default-slot contract. |
AttachmentState | Supported lifecycle-state union. |
AttachmentSize | Supported density union. |
AttachmentOrientation | Supported orientation union. |
AttachmentMediaVariant | Supported media-variant union. |
AttachmentButtonType | Supported native-button-type union. |
AttachmentTriggerElement | Supported trigger-element union. |