npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@botandrose/input-attachment

v0.6.4

Published

Next-gen file field

Downloads

35

Readme

input-attachment

A web components library providing advanced file upload functionality for Rails Active Storage with drag-and-drop support and real-time progress tracking.

Features

  • 📁 Multiple File Support - Upload single or multiple files
  • 🎨 Drag & Drop - Intuitive drag-and-drop file selection
  • 📸 File Previews - Automatic preview generation for images and videos
  • 📊 Progress Tracking - Real-time upload progress with visual feedback
  • ✅ Validation - Built-in file type and size validation
  • 🔐 Rails Active Storage Integration - Seamless direct uploads to AWS S3 or other storage backends

Installation

npm install @botandrose/input-attachment

or with Bun:

bun add @botandrose/input-attachment

Usage

Basic Example

<form>
  <input-attachment
    name="files"
    directupload="/rails/active_storage/direct_uploads"
    multiple
  ></input-attachment>

  <button type="submit">Upload</button>
</form>

With Validation

<input-attachment
  name="photos"
  directupload="/rails/active_storage/direct_uploads"
  accepts="image"
  max="5242880"
  required
></input-attachment>

API Reference

<input-attachment>

The main component providing file upload functionality.

Props

| Prop | Type | Default | Description | | ------ | ------ | --------- | ------------- | | name | string | - | Form field name for form submission | | directupload | string | - | Rails Active Storage direct upload endpoint URL | | multiple | boolean | false | Allow multiple file selection | | required | boolean | false | Require at least one file | | accepts | string | - | Comma-separated file types (e.g., "image", "video", "pdf") | | max | number | - | Maximum file size in bytes | | preview | boolean | true | Show file previews | | disabled | boolean | false | Disable file selection (used during form submission) |

Methods

// Get/set array of file objects
get files(): AttachmentFile[]
set files(val: AttachmentFile[])

// Get/set the attachments as a JSON string of { value: signed_id, filename, src, ... } objects
get value(): string
set value(val: string)

// Clear all files
reset(): void

// Validate the component
checkValidity(): boolean
setCustomValidity(msg: string): void
reportValidity(): boolean

// Get validation error message
get validationMessage(): string

Events

| Event | Detail | Description | | ------- | -------- | ------------- | | change | - | Fired when file list changes (bubbles) | | direct-upload:initialize | { id, file, controller } | Upload queue initialized | | direct-upload:start | { id } | Upload started | | direct-upload:progress | { id, progress } | Upload progress (0-100) | | direct-upload:error | { id, error } | Upload failed | | direct-upload:end | { id } | Upload completed |

<attachment-file>

Individual file representation within the upload component.

Props

| Prop | Type | Default | Description | | ------ | ------ | --------- | ------------- | | name | string | - | Form field name | | value | string | "" | Signed ID from Rails Active Storage | | filename | string | - | Display filename | | src | string | - | Preview image/video URL | | filetype | string | - | File category (image/video/pdf/unknown) | | size | number | - | File size in bytes | | state | string | "complete" | Upload state (pending/complete/error) | | percent | number | 100 | Upload progress percentage | | preview | boolean | true | Show preview | | accepts | string | - | Allowed file types | | max | number | - | Maximum file size |

Methods

// Set file to upload
set file(file: File)

// Load existing file from Active Storage
set signedId(val: string)

// Validate the file
checkValidity(): boolean

Outside Rails (cross-origin, token auth)

directupload can be an absolute URL on another origin, as long as that endpoint speaks the @rails/activestorage protocol and the storage bucket's CORS allows your origin. Add credentials from the direct-upload:before-blob-request event, which carries the XHR for the blob-reserving request:

const el = document.querySelector("input-attachment")
el.addEventListener("direct-upload:before-blob-request", event => {
  event.detail.xhr.setRequestHeader("Authorization", `Bearer ${token}`)
})

The element registers itself only after defineCustomElements() runs, so in a framework call it once on the client:

import { defineCustomElements } from "@botandrose/input-attachment"
defineCustomElements()

Without a <form> submit, read the signed id from the change event: value is a JSON string of the current attachments, and each one's value is its signed id.

el.addEventListener("change", () => {
  const [attachment] = JSON.parse(el.value)
  console.log(attachment?.value) // the signed id, or undefined once removed
})

Form Submission Flow

  1. User selects/drops files into <input-attachment>
  2. Files become <attachment-file> components with validation
  3. On form submit, FormController intercepts and manages upload queue
  4. Each file uploads to Rails Active Storage via DirectUploadController
  5. Signed IDs are collected via ElementInternals.setFormValue()
  6. After all uploads complete, form is actually submitted
  7. Server receives signed IDs in form data

Architecture

Components

  • <input-attachment> - Main form field replacement with drag-and-drop
  • <attachment-file> - Individual file representation
  • <attachment-preview> - File preview display
  • <file-drop> - Drag and drop interface (from @botandrose/file-drop)
  • <progress-bar> - Upload progress indicator (from @botandrose/progress-bar)

Styling

The components use Shadow DOM, so external CSS cannot reach their internals. Instead, all visual properties are exposed as CSS custom properties that pierce the shadow boundary. Set them on the input-attachment element or any ancestor.

CSS Custom Properties

Text & Drop Zone

| Property | Default | Description | | -------- | ------- | ----------- | | --input-attachment-text-color | #000 | Main text color | | --input-attachment-drop-bg | rgba(255,255,255, 0.25) | Drop zone background | | --input-attachment-drop-border | rgba(0,0,0, 0.25) | Drop zone dashed border | | --input-attachment-drop-color | #444 | Drop zone text color | | --input-attachment-drop-bg-active | rgba(255,255,255, 0.5) | Drop zone background during drag-over |

Upload Dialog

| Property | Default | Description | | -------- | ------- | ----------- | | --input-attachment-overlay-bg | rgba(51, 51, 51, 0.9) | Modal overlay background | | --input-attachment-dialog-bg | #fcfcfc | Dialog content background | | --input-attachment-dialog-border | #1f1f1f | Dialog heading border |

Errors

| Property | Default | Description | | -------- | ------- | ----------- | | --input-attachment-error-color | #c00 | Validation error and retry link text | | --input-attachment-error-color-hover | #900 | Retry link hover color | | --input-attachment-error-bg | rgba(74, 70, 70, 0.25) | Error state progress bar background |

Parts

Style the file-drop title using the ::part() pseudo-element:

input-attachment::part(title) {
  font-size: 16px;
}

Rails Integration

Setup Active Storage Direct Uploads

In your Rails app, ensure Active Storage is configured:

# config/storage.yml
amazon:
  service: S3
  access_key_id: ...
  secret_access_key: ...

The directupload prop should point to:

/rails/active_storage/direct_uploads

Accessing Uploaded Files in Rails

class Post < ApplicationRecord
  has_many_attached :attachments
end

# In controller
@post = Post.create(attachments: attachment_signed_ids)

# The signed_ids are automatically resolved to blobs

Validation

File Type Validation

<input-attachment
  accepts="image,video"
></input-attachment>

Supported types: image, video, pdf, or specific MIME types

File Size Validation

<!-- Max 5MB -->
<input-attachment
  max="5242880"
></input-attachment>

Custom Validation

const attachment = document.querySelector('input-attachment');

// Check validity
if (!attachment.checkValidity()) {
  console.log(attachment.validationMessage);
}

// Set custom error
attachment.setCustomValidity('Custom error message');

Development

Prerequisites

  • Node.js 18+
  • Bun

Commands

# Install dependencies
bun install

# Start development server
bun start

# Build for production
bun run build

# Run tests
bun run test

# Watch tests
bun run test.watch

# Run e2e tests only
bun run test:e2e

# Generate new component
bun run generate

Browser Support

  • Chrome/Edge 88+
  • Firefox 85+
  • Safari 15.1+

ElementInternals requires modern browsers with form-associated custom elements support.

License

MIT