Skip to content
Kappa
Attachment
@dicehub/kappav0.4.2

Attachment

Presents files, upload progress, previews, and independent actions in a compact compound component.

pressure.png contour preview
pressure.pngPNG · 1.8 MB
velocity.png contour preview
velocity.pngPNG · 2.1 MB
vorticity.png contour preview
vorticity.pngPNG · 940 KB
volume-mesh.mshUploading · 64%
solver-log.txtText · 184 KB
<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.

rotor-case.casFluent case · 24.6 MB
<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

Typical part hierarchy
Attachment.Group
└── Attachment.Root
    ├── Attachment.Media
    ├── Attachment.Content
    │   ├── Attachment.Title
    │   └── Attachment.Description
    ├── Attachment.Actions
    │   └── Attachment.Action
    └── Attachment.Trigger

The compound API and named exports are equivalent. Use named parts when explicit imports make a shared file-row composition easier to inspect.

forces.csvCSV · 86 KB
<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.

pressure.png contour preview
pressure.pngPNG · 1.8 MB
velocity.png contour preview
velocity.pngPNG · 2.1 MB
vorticity.png contour preview
vorticity.pngPNG · 940 KB
<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.

geometry.stepReady to upload
volume-mesh.mshUploading · 64%
run-042.foamIndexing result fields
boundary-map.jsonUpload failed. Check the file and try again.
solver-log.txtText · 184 KB
<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.

default · residuals.csvCSV · 96 KB
sm · residuals.csvCSV · 96 KB
xs · residuals.csvCSV · 96 KB
<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.

horizontal.foamOpenFOAM case
Velocity contour preview
velocity.pngPNG · 2.1 MB
<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.

rotor-surface.stlSTL · 18.2 MB
Pressure field preview
pressure-field.vtkVTK · 42.7 MB
solver-log.txtText · 184 KB
residuals.csvCSV · 96 KB
<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.

rotor-case.casFluent case · 24.6 MB
solver-log.txtText · 184 KB
<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.

نتائج-الضغط.csvCSV · ١٢٨ كيلوبايت
<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", or aria-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.

PropTypeDefaultDescription
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

PropTypeDefaultDescription
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.

PropTypeDefaultDescription
disabledbooleanfalseDisables the native icon-only action button.
type"button" | "submit" | "reset""button"Sets the native button type.

Attachment.Trigger

PropTypeDefaultDescription
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

PartElementDescription
Attachment.ContentdivFlexible wrapper for the title and supporting metadata.
Attachment.TitlespanSingle-line file name or primary label.
Attachment.DescriptionspanSupporting metadata, progress, or error text.
Attachment.ActionsdivStacking boundary that keeps controls above a full-card trigger.
Attachment.ActionbuttonNative button with type=button by default.
Attachment.GroupdivHorizontally scrollable, snap-aligned collection.

Exports

ExportDescription
AttachmentCompound API exposing Root, Media, Content, Title, Description, Actions, Action, Trigger, and Group.
AttachmentRootUnaugmented attachment root.
AttachmentMediaIcon or image media frame.
AttachmentContentFile-information wrapper.
AttachmentTitlePrimary file label.
AttachmentDescriptionMetadata, progress, or error copy.
AttachmentActionsContainer for independent attachment actions.
AttachmentActionNative button action.
AttachmentTriggerFull-card button or anchor trigger.
AttachmentGroupScrollable attachment collection.
ATTACHMENT_STATESReadonly list of supported lifecycle states.
ATTACHMENT_SIZESReadonly list of supported densities.
ATTACHMENT_ORIENTATIONSReadonly list of supported orientations.
ATTACHMENT_MEDIA_VARIANTSReadonly list of media treatments.
ATTACHMENT_BUTTON_TYPESReadonly list of native button types.
ATTACHMENT_TRIGGER_ELEMENTSReadonly list of supported trigger elements.
ATTACHMENT_DEFAULT_STATEDefault lifecycle state: done.
ATTACHMENT_DEFAULT_SIZEDefault density: default.
ATTACHMENT_DEFAULT_ORIENTATIONDefault orientation: horizontal.
ATTACHMENT_MEDIA_DEFAULT_VARIANTDefault media treatment: icon.
ATTACHMENT_ACTION_DEFAULT_TYPEDefault native action type: button.
ATTACHMENT_TRIGGER_DEFAULT_ELEMENTDefault trigger element: button.
ATTACHMENT_TRIGGER_DEFAULT_TYPEDefault native trigger type: button.
isAttachmentStateRuntime state type guard.
isAttachmentSizeRuntime size type guard.
isAttachmentOrientationRuntime orientation type guard.
isAttachmentMediaVariantRuntime media-variant type guard.
isAttachmentButtonTypeRuntime native-button-type guard.
isAttachmentTriggerElementRuntime trigger-element guard.
resolveAttachmentStateResolves unknown input to a supported state.
resolveAttachmentSizeResolves unknown input to a supported size.
resolveAttachmentOrientationResolves unknown input to a supported orientation.
resolveAttachmentMediaVariantResolves unknown input to a supported media variant.
resolveAttachmentButtonTypeResolves unknown input to a native button type.
resolveAttachmentTriggerElementResolves unknown input to a trigger element.
AttachmentPropsAlias of the public root props.
AttachmentRootPropsPublic root props.
AttachmentMediaPropsPublic media props.
AttachmentActionPropsPublic action props.
AttachmentTriggerPropsPublic trigger props.
AttachmentSlotsAlias of the public root slot contract.
AttachmentRootSlotsRoot default-slot contract.
AttachmentMediaSlotsMedia default-slot contract.
AttachmentContentSlotsContent default-slot contract.
AttachmentTitleSlotsTitle default-slot contract.
AttachmentDescriptionSlotsDescription default-slot contract.
AttachmentActionsSlotsActions default-slot contract.
AttachmentActionSlotsAction default-slot contract.
AttachmentTriggerSlotsTrigger default-slot contract.
AttachmentGroupSlotsGroup default-slot contract.
AttachmentStateSupported lifecycle-state union.
AttachmentSizeSupported density union.
AttachmentOrientationSupported orientation union.
AttachmentMediaVariantSupported media-variant union.
AttachmentButtonTypeSupported native-button-type union.
AttachmentTriggerElementSupported trigger-element union.