Attachments
nexus-ui/attachmentsFreeComponent
Composable file attachments for chat inputs and messages with preview, variants, and upload wiring
Live preview
live · rendered by the registry
Rendered by nexus-ui on their own page — this is the component running, not a screenshot of it.Install it
npx shadcn@latest add https://nexus-ui.dev/r/attachments.jsonSource
1"use client";23import * as React from "react";4import { createPortal } from "react-dom";5import { Slot } from "@radix-ui/react-slot";6import { cva, type VariantProps } from "class-variance-authority";7import {8 Cancel01Icon,9 Image02Icon,10 File02Icon,11 Video02Icon,12 MusicNote02Icon,13 Upload01Icon,14} from "@hugeicons/core-free-icons";15import { HugeiconsIcon } from "@hugeicons/react";16import { useOnChange } from "@/lib/use-on-change";1718import { cn } from "@/lib/utils";1920// ——— Metadata schema (single source) ———2122/** Upload or message attachment metadata. */23export interface AttachmentMeta {24 type: "image" | "file" | "video" | "audio";25 name?: string;26 url?: string;27 /** Raster preview URL (e.g. PDF first page). When unset, preview uses the icon for `type`. */28 thumbnailUrl?: string;29 mimeType?: string;30 size?: number;31 width?: number;32 height?: number;33 /** Binary payload when not using `url` (browser-friendly; prefer `Blob`). */34 data?: Blob | ArrayBuffer;35 /**36 * **`"paste"`** when created from clipboard (e.g. long text → **`File`**). Use with **`Attachment`** **`variant="pasted"`**.37 */38 source?: "paste";39}4041/** Files not appended by the picker: oversized, over `maxFiles`, extra when `multiple` is false, or outside `accept`. */42export type AttachmentsRejectedFiles = {43 tooLarge: File[];44 /** Within size but did not fit under `maxFiles`. */45 overMaxFiles: File[];46 /** Ignored because `multiple` is false. */47 truncatedByMultiple: File[];48 /** Did not match the root `accept` string (drop path or permissive pickers). */49 notAccepted: File[];50};5152export function toAttachmentMeta(53 file: File,54 options?: { objectUrl?: string; source?: AttachmentMeta["source"] },55): AttachmentMeta {56 const mime = file.type?.toLowerCase() ?? "";57 let kind: AttachmentMeta["type"] = "file";58 if (mime.startsWith("image/")) kind = "image";59 else if (mime.startsWith("video/")) kind = "video";60 else if (mime.startsWith("audio/")) kind = "audio";6162 return {63 type: kind,64 name: file.name,65 url: options?.objectUrl,66 mimeType: file.type || undefined,67 size: file.size,68 ...(options?.source != null ? { source: options.source } : {}),69 };70}7172/** Optional second argument to **`appendFiles`** (from **`useAttachments`**). */73export type AppendFilesOptions = {74 /** Sets **`AttachmentMeta.source`** to **`"paste"`** for every appended item. */75 paste?: boolean;76};7778/**79// … truncated
What it pulls in
npm packages
Files it writes
More from nexus-ui
All 15 items| Component | Registry | Kind | Access | Installs | Command |
|---|---|---|---|---|---|
| Chain of Thoughtchain-of-thought | nexus-ui | Components | Free | 2 deps · 2 files | |
| Citationcitation | nexus-ui | Components | Free | 4 deps | |
| Feedback Barfeedback-bar | nexus-ui | Components | Free | 1 dep | |
| Imageimage | nexus-ui | Components | Free | 2 deps | |
| Messagemessage | nexus-ui | Components | Free | 9 deps · 2 files | |
| Model Selectormodel-selector | nexus-ui | Components | Free | 3 deps | |
| Prompt Inputprompt-input | nexus-ui | Components | Free | 1 dep | |
| Questionsquestions | nexus-ui | Components | Free | 2 deps |
