sdxc

Type to search, or start from one of these:

Attachment

A compound card presenting one attached file or image.

q3-board-review.pdf

2.4 MB · 18 pages

launch-hero@2x.png

6.1 MB · 68% uploaded

renewals-2026.csv

Checking 12,480 rows

contract-final.docx

Over the 25 MB limit

q3-board-review.pdf

The card opened this. Pressing the download button inside it did not.

View code
<>
	<Attachment.Group aria-label="Attachments on this message" mix={[maxIs("34rem")]}>
		<Attachment.Trigger
			commandfor="preview-attachment-dialog"
			mix={[is("17rem"), attachmentTrigger()]}
		>
			<Attachment state="done">
				<Attachment.Media>
					<FileTextIcon aria-hidden="true" />
				</Attachment.Media>
				<Attachment.Content>
					<Attachment.Title style={{ opacity: 1 }}>q3-board-review.pdf</Attachment.Title>
					<Attachment.Description>2.4 MB · 18 pages</Attachment.Description>
				</Attachment.Content>
				<Attachment.Actions>
					<Attachment.Action aria-label="Download q3-board-review.pdf">
						<DownloadIcon />
					</Attachment.Action>
				</Attachment.Actions>
			</Attachment>
		</Attachment.Trigger>

		<Attachment state="uploading" mix={[is("17rem")]}>
			<Attachment.Media>
				<ImageIcon aria-hidden="true" />
			</Attachment.Media>
			<Attachment.Content>
				<Attachment.Title state="uploading" style={{ opacity: 1 }}>launch-hero@2x.png</Attachment.Title>
				<Attachment.Description>6.1 MB · 68% uploaded</Attachment.Description>
			</Attachment.Content>
		</Attachment>

		<Attachment state="processing" mix={[is("17rem")]}>
			<Attachment.Media>
				<FileSpreadsheetIcon aria-hidden="true" />
			</Attachment.Media>
			<Attachment.Content>
				<Attachment.Title state="processing" style={{ opacity: 1 }}>renewals-2026.csv</Attachment.Title>
				<Attachment.Description>Checking 12,480 rows</Attachment.Description>
			</Attachment.Content>
		</Attachment>

		<Attachment state="error" mix={[is("17rem")]}>
			<Attachment.Media>
				<FileTextIcon aria-hidden="true" />
			</Attachment.Media>
			<Attachment.Content>
				<Attachment.Title style={{ opacity: 1 }}>contract-final.docx</Attachment.Title>
				<Attachment.Description>Over the 25 MB limit</Attachment.Description>
			</Attachment.Content>
			<Attachment.Actions>
				<Attachment.Action aria-label="Retry contract-final.docx" color="danger" variant="ghost">
					<RefreshCwIcon />
				</Attachment.Action>
				<Attachment.Action aria-label="Remove contract-final.docx" color="danger" variant="ghost">
					<Trash2Icon />
				</Attachment.Action>
			</Attachment.Actions>
		</Attachment>
	</Attachment.Group>

	<Dialog id="preview-attachment-dialog" aria-labelledby="preview-attachment-dialog-title">
		<Dialog.Header>
			<Dialog.Title id="preview-attachment-dialog-title">q3-board-review.pdf</Dialog.Title>
			<Dialog.Description>
				The card opened this. Pressing the download button inside it did not.
			</Dialog.Description>
		</Dialog.Header>
		<Dialog.Close commandfor="preview-attachment-dialog" aria-label="Close" />
	</Dialog>
</>

Installation

An ordinary dependency: install it, import the theme once, and import the component where it is used.

npm add @sdxc/ui
import "@sdxc/ui/theme.css";

Usage

import { Attachment } from "@sdxc/ui";

The card's outer host, recolored to the danger tint at state="error" and declaring the ui-attachment named container so nested parts reflow at the card's width. A busy state sets aria-busy to the token ARIA recognizes.

Composition

The parts the component publishes, each a static property of the host.

  • Attachment
  • Attachment.MediaThe card's media well: a fixed-size, rounded box clipping the preview a consumer nests inside.
  • Attachment.ContentThe card's text column, growing into whatever inline space Attachment.Media and Attachment.Actions leave it.
  • Attachment.TitleThe file or image name in a native <p>, truncated to a single line with an ellipsis.
  • Attachment.DescriptionThe card's supporting detail line: a muted, single-line <p> carrying a file size, a page count, or the explanation of what went wrong once the ancestor Attachment's state reads "error".
  • Attachment.ActionsThe card's action row, shrink-proof so a narrow card's width is absorbed by Attachment.Content's own truncation while the actions keep their size.
  • Attachment.ActionA single card action as a Button, defaulting to a compact, "ghost", neutral control so a row of actions reads as secondary to the card's title.
  • Attachment.TriggerA wrapper <div> whose href or commandfor stays an inert attribute until a consumer mixes in attachmentTrigger().
  • Attachment.GroupA horizontally scroll-snapping row of cards, faded at both inline edges to hint at cards beyond the current view.

Examples

<Attachment state="error">
	<Attachment.Media><FileIcon aria-hidden /></Attachment.Media>
	<Attachment.Content>
		<Attachment.Title>quarterly-report.pdf</Attachment.Title>
		<Attachment.Description>{t("attachment.uploadFailed")}</Attachment.Description>
	</Attachment.Content>
	<Attachment.Actions>
		<Attachment.Action aria-label={t("attachment.retry")}><RotateCwIcon /></Attachment.Action>
	</Attachment.Actions>
</Attachment>

Props

Read from the component's own types, so every prop, every default and every allowed value is listed.

PropTypeDescription
state?"idle" | "uploading" | "processing" | "error" | "done"Lifecycle state of the file or image the card represents. Defaults to "idle".
childrenRemixNodeThe card's compound parts: Attachment.Media, Attachment.Content, and Attachment.Actions.

Also accepts everything in TagProps<"div">.

Attachment.Media

PropTypeDescription
childrenRemixNodeThe preview itself: an <img>, or a decorative icon standing in for the file's type.

Also accepts everything in TagProps<"div">.

Attachment.Content

PropTypeDescription
childrenRemixNodeAttachment.Title and, optionally, Attachment.Description.

Also accepts everything in TagProps<"div">.

Attachment.Title

PropTypeDescription
state?Attachment.StateThe ancestor Attachment's own state, repeated here because a component reads only its own props; passing the same value to both is what keeps them in sync. Left unset, the title renders plain.
childrenRemixNodeThe file or image name.

Also accepts everything in TagProps<"p">.

Attachment.Description

PropTypeDescription
childrenRemixNodeSupporting detail — a file size, a page count, an error message.

Also accepts everything in TagProps<"p">.

Attachment.Actions

PropTypeDescription
childrenRemixNodeA run of Attachment.Action controls.

Also accepts everything in TagProps<"div">.

Attachment.Action

Also accepts everything in Button.Props.

Attachment.Trigger

PropTypeDescription
href?stringDestination the whole card follows once attachmentTrigger() is mixed in, opened in place unless the activation asks for a new tab. It rides on a <div> so a nested Attachment.Action stays valid markup.
commandfor?stringid of a <dialog> element the whole card opens once attachmentTrigger() is mixed in, instead of following href.
command?"show-modal"Invoker Commands verb dispatched to commandfor's dialog. Defaults to "show-modal".
target?stringWhere href opens: a window/frame name, or "_blank" for a new tab. Meaningful only alongside href.
childrenRemixNodeThe card's own markup — typically an Attachment.

Also accepts everything in TagProps<"div">.

Attachment.Group

PropTypeDescription
childrenRemixNodeA run of Attachment or Attachment.Trigger cards.

Also accepts everything in TagProps<"div">.

Types

NameValuesWhat it decides
State"idle" | "uploading" | "processing" | "error" | "done"Lifecycle state of a card's file or image.