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

@xsolla/xui-uploader

v0.191.0

Published

A cross-platform React file uploader component that provides a button to select files, displays selected files, and allows removing them. <!-- BEGIN:xui-mcp-instructions:uploader --> A compact button-style control that triggers a file selection dialog and

Readme

Uploader

A cross-platform React file uploader component that provides a button to select files, displays selected files, and allows removing them.

A compact button-style control that triggers a file selection dialog and optionally displays a list of uploaded or selected files below the trigger. Supports five sizes, six states including an upload-in-progress loading state, and a toggleable file list. Used wherever a user must attach, replace, or upload a file — profile photos, document uploads, asset imports.

When to use

  • When the user must select and upload a file from their device — profile picture, document, CSV import, media asset
  • When the file selection happens inline in a form alongside other fields
  • When the number of accepted files is small (one or a few) and a full drag-and-drop zone would occupy too much space
  • When the upload state and error feedback must be communicated within the field itself, without a separate Toast or modal

When not to use

When batch uploading many files at once with drag-and-drop is the primary interaction — use a dedicated Drag&dropUploader component

When the upload triggers a complex multi-step flow — open a dedicated upload modal instead

When only a URL or link is needed, not an actual file — use a standard Input

Content guidelines

Trigger label — use a short imperative phrase: "Upload file", "Choose file", "Add document", "Upload photo". For replace scenarios, switch to "Replace file" or "Change photo".

Loading label — show "Uploading…" or "Processing…" while in State=Loading. If the upload has measurable progress, include the percentage: "Uploading… 64%". Error messages — be specific and actionable:

  • "File type not supported. Please upload a PDF or DOCX file."
  • "File is too large. Maximum allowed size is 10 MB."
  • "Upload failed. Check your connection and try again."
  • "Please upload a file to continue." (required field, empty on submit)
  • File type guidance — add helper text below the component (outside FileUploader) specifying accepted formats and size limits: "Accepted formats: PDF, DOCX. Max size: 10 MB." Do not put this information inside the trigger label.
  • Field label — always provide a visible label above the component: "Profile photo", "Company logo", "Import file". Do not rely on the trigger label alone.

Behaviour guidelines (from industry practice)

Click to select — clicking the trigger opens the native OS file selection dialog. After the user selects a file, the component transitions based on the product's flow: either immediately begins uploading (State=Loading) or adds the file to the list (if deferred upload is used).

Loading state — switch to State=Loading immediately when the upload begins. The trigger is non-interactive during upload. Show a progress indicator if the file is large and upload duration is meaningful (e.g. a progress bar inside or below the trigger, outside the component itself).

Upload complete — after a successful upload, return the trigger to State=Default (or a custom "Replace" state). If Uploader list=true, add the uploaded file to the list. Error handling — switch to State=Error when:

  • The file type is not accepted (e.g. a PNG was uploaded but only PDF is allowed)
  • The file size exceeds the maximum allowed
  • The upload request fails on the server
  • Required file was not provided on form submission Show a specific error message below the trigger.
  • Remove file — clicking the ✕ button in an uploader list item removes that file from the selection. If the product uses immediate upload, also cancel or delete the server-side upload. After removal, if the list is empty, keep Uploader list=true visible (empty state) or revert to Uploader list=false depending on the product logic.
  • Replace file — for single-file contexts, clicking the trigger again while a file is already selected or uploaded should replace the previous selection. Provide a clear label change (e.g. "Replace file") to signal this behaviour.
  • File validation — validate the file type and size client-side before uploading and before switching to State=Loading. Do not start an upload for an invalid file — switch directly to State=Error with a specific message.
  • Disabled state — State=Disable prevents opening the file dialog. Show a tooltip or nearby explanation for why the upload is unavailable.
  • Multiple files — if the product allows multiple file selection, set multiple on the underlying <input type="file"> and enable Uploader list=true to show all selected files. Define a clear max file count and enforce it client-side.

Accessibility

The trigger must be implemented as a or element associated with a visually hidden <input type="file">. The must not be the visible trigger.

The trigger must have aria-label if the visible label text is not descriptive enough — e.g. aria-label="Upload profile photo".

When State=Loading, set aria-busy="true" on the trigger and update aria-label to reflect the in-progress state: aria-label="Uploading profile photo…".

When State=Error, the error message must be associated via aria-describedby so screen readers announce it when the trigger receives focus.

When State=Disable, the trigger must have aria-disabled="true". Keep it focusable so screen readers can discover and announce it.

The file list (when Uploader list=true) must be in a region with aria-label="Selected files" or equivalent. Each file item must be announced with its name and size. The remove button for each item must have aria-label="Remove [file name]" — not just aria-label="Remove".

When a file is removed from the list, announce the removal to screen readers using aria-live="polite": e.g. "document.pdf removed".

When a file is successfully uploaded or added to the list, use aria-live="polite" to announce: "document.pdf added".

The accepted file types and size limits (shown in helper text) should be linked to the trigger via aria-describedby so screen readers surface the constraints when the trigger is focused.

Installation

npm install @xsolla/xui-uploader

Demo

Basic Uploader

import * as React from "react";
import { Uploader } from "@xsolla/xui-uploader";

export default function BasicUploader() {
  return (
    <Uploader
      label="Upload file"
      onFilesChange={(files) => console.log("Selected:", files)}
    />
  );
}

Multiple Files

import * as React from "react";
import { Uploader } from "@xsolla/xui-uploader";

export default function MultipleFiles() {
  return (
    <Uploader
      label="Upload documents"
      placeholder="Select multiple files"
      multiple
      onFilesChange={(files) => console.log("Files:", files)}
    />
  );
}

Accept Specific Types

import * as React from "react";
import { Uploader } from "@xsolla/xui-uploader";

export default function ImageUploader() {
  return (
    <Uploader
      label="Upload image"
      placeholder="Select image file"
      accept=".jpg,.jpeg,.png,.gif"
      onFilesChange={(files) => console.log("Images:", files)}
    />
  );
}

Anatomy

import { Uploader } from "@xsolla/xui-uploader";

<Uploader
  label="Label" // Label above button
  placeholder="Select files" // Button text
  accept=".pdf,.doc" // Accepted file types
  multiple={false} // Allow multiple selection
  disabled={false} // Disabled state
  size="md" // Button size
  onFilesChange={handleFiles} // File change callback
/>;

Examples

Document Upload

import * as React from "react";
import { Uploader } from "@xsolla/xui-uploader";

export default function DocumentUpload() {
  const [files, setFiles] = React.useState<File[]>([]);

  return (
    <div>
      <Uploader
        label="Legal documents"
        placeholder="Upload PDF documents"
        accept=".pdf"
        multiple
        onFilesChange={setFiles}
      />
      {files.length > 0 && (
        <p style={{ marginTop: 8 }}>{files.length} file(s) selected</p>
      )}
    </div>
  );
}

Profile Image Upload

import * as React from "react";
import { Uploader } from "@xsolla/xui-uploader";

export default function ProfileImageUpload() {
  const handleImageUpload = (files: File[]) => {
    if (files.length > 0) {
      const file = files[0];
      // Create preview URL
      const previewUrl = URL.createObjectURL(file);
      console.log("Preview:", previewUrl);
    }
  };

  return (
    <div style={{ maxWidth: 300 }}>
      <Uploader
        label="Profile picture"
        placeholder="Choose image"
        accept="image/*"
        onFilesChange={handleImageUpload}
        size="sm"
      />
      <p style={{ fontSize: 12, color: "#666", marginTop: 4 }}>
        Recommended: Square image, at least 200x200px
      </p>
    </div>
  );
}

Form with File Upload

import * as React from "react";
import { Uploader } from "@xsolla/xui-uploader";
import { Input } from "@xsolla/xui-input";
import { Textarea } from "@xsolla/xui-textarea";
import { Button } from "@xsolla/xui-button";

export default function FormWithUpload() {
  const [files, setFiles] = React.useState<File[]>([]);

  const handleSubmit = () => {
    const formData = new FormData();
    files.forEach((file) => formData.append("attachments", file));
    console.log("Submitting with", files.length, "files");
  };

  return (
    <form
      style={{
        display: "flex",
        flexDirection: "column",
        gap: 16,
        maxWidth: 400,
      }}
      onSubmit={(e) => {
        e.preventDefault();
        handleSubmit();
      }}
    >
      <Input label="Subject" placeholder="Enter subject" />
      <Textarea label="Message" placeholder="Enter your message" />
      <Uploader
        label="Attachments"
        placeholder="Add attachments"
        multiple
        onFilesChange={setFiles}
      />
      <Button type="submit">Submit</Button>
    </form>
  );
}

Different Sizes

import * as React from "react";
import { Uploader } from "@xsolla/xui-uploader";

export default function UploaderSizes() {
  return (
    <div style={{ display: "flex", flexDirection: "column", gap: 16 }}>
      <Uploader size="xs" placeholder="Extra small" />
      <Uploader size="sm" placeholder="Small" />
      <Uploader size="md" placeholder="Medium" />
      <Uploader size="lg" placeholder="Large" />
      <Uploader size="xl" placeholder="Extra large" />
    </div>
  );
}

Disabled State

import * as React from "react";
import { Uploader } from "@xsolla/xui-uploader";

export default function DisabledUploader() {
  return (
    <Uploader
      label="Upload (disabled)"
      placeholder="Cannot upload files"
      disabled
    />
  );
}

API Reference

Uploader

UploaderProps:

| Prop | Type | Default | Description | | :------------ | :------------------------------------- | :------------------------- | :------------------------------------------------------------------------------------------------------------ | | testID | string | — | Test ID for testing frameworks. On web this renders as data-testid; on React Native it renders as testID. | | label | string | - | Label text above the button. | | placeholder | string | "Select files to upload" | Button text. | | accept | string | - | Accepted file types (e.g., ".jpg,.png"). | | multiple | boolean | false | Allow multiple file selection. | | disabled | boolean | false | Disabled state. | | size | "xs" \| "sm" \| "md" \| "lg" \| "xl" | "md" | Button size. | | onFilesChange | (files: File[]) => void | - | Callback when files change. |

File List Display

When files are selected, they appear below the button with:

  • File icon
  • File name (truncated if too long)
  • Remove button (X icon)

Removing a file updates the list and calls onFilesChange with the updated array.

Accept Patterns

| Pattern | Description | | :---------------- | :---------------------- | | .pdf | PDF files only | | .jpg,.jpeg,.png | Specific image formats | | image/* | All image types | | video/* | All video types | | .doc,.docx,.pdf | Multiple document types |

Behavior

  • Click button to open native file picker
  • Multiple selection when multiple is true
  • Files accumulate when multiple is true (new selections add to existing)
  • Files replace when multiple is false
  • Each file can be removed individually
  • Hidden native input, styled button

Accessibility

  • Button is keyboard accessible
  • File input is properly associated with the button
  • Remove buttons have appropriate labels
  • Disabled state prevents interaction
  • File list is accessible to screen readers