@manufosela/rich-inputfile

@manufosela/rich-inputfile

Enhanced file input web component with preview and validation

Rich Inputfile

Enhanced file input with drag and drop, previews, and validation.

Basic Usage

Drag and drop or click to select a file.

Select a file...
<rich-inputfile label="Upload File"></rich-inputfile>

Image Upload with Preview

Images automatically show a thumbnail preview.

<rich-inputfile label="Upload Image" allowed-extensions="jpg,jpeg,png,gif,webp" ></rich-inputfile>

File Type Restriction

Only allow specific file types.

Only PDF and Word documents allowed
<rich-inputfile label="Upload Document" allowed-extensions="pdf,doc,docx" ></rich-inputfile>

Size Limit

Restrict maximum file size.

Maximum 1MB file size
<rich-inputfile label="Upload (max 1MB)" max-size="1048576" ></rich-inputfile>

No Preview

Disable thumbnail preview for images.

<rich-inputfile label="Upload File (no preview)" show-preview="false" ></rich-inputfile>

Disabled State

Disable the file input.

<rich-inputfile label="Upload (disabled)" disabled></rich-inputfile>

No Dropzone

Click-only mode without drag and drop.

<rich-inputfile label="Click to Upload" dropzone="false" ></rich-inputfile>

Custom Preview Size

Adjust the thumbnail preview size.

<rich-inputfile label="Large Preview" preview-size="100" ></rich-inputfile>

Programmatic Control

Control the component via JavaScript.

Use buttons to interact
// Get file const file = inputfile.getFile(); // Get as ArrayBuffer const buffer = await inputfile.getFileArrayBuffer(); // Get as Data URL const dataUrl = await inputfile.getFileDataURL(); // Clear file inputfile.clear();
Demo code (CodePen-ready HTML, CSS, JS)
HTML (html)
<h1>Rich Inputfile</h1>
<p class="subtitle">Enhanced file input with drag and drop, previews, and validation.</p>
<div class="demo-links">
  <a href="https://manufosela.dev/ui-components/">← Back to components</a>
  <a href="https://github.com/manufosela/ui-components/tree/main/packages/rich-inputfile" target="_blank" rel="noopener">GitHub Repo</a>
  <a href="playground.html">Playground</a>
  <a href="https://www.npmjs.com/package/@manufosela/rich-inputfile" target="_blank" rel="noopener">npm</a>
</div>
<div class="demo-theme-toggle">
  <theme-toggle theme="dark"></theme-toggle>
</div>
<div class="demo-section">
    <h2>Basic Usage</h2>
    <p>Drag and drop or click to select a file.</p>
    <rich-inputfile id="basic" label="Upload File"></rich-inputfile>
    <div class="output" id="basicOutput">Select a file...</div>
    <div class="code-block">&lt;rich-inputfile label="Upload File"&gt;&lt;/rich-inputfile&gt;</div>
      
  </div>

  <div class="demo-section">
    <h2>Image Upload with Preview</h2>
    <p>Images automatically show a thumbnail preview.</p>
    <rich-inputfile
      id="image"
      label="Upload Image"
      allowed-extensions="jpg,jpeg,png,gif,webp"
    ></rich-inputfile>
    <div class="code-block">&lt;rich-inputfile
  label="Upload Image"
  allowed-extensions="jpg,jpeg,png,gif,webp"
&gt;&lt;/rich-inputfile&gt;</div>
  </div>

  <div class="demo-section">
    <h2>File Type Restriction</h2>
    <p>Only allow specific file types.</p>
    <rich-inputfile
      id="docs"
      label="Upload Document"
      allowed-extensions="pdf,doc,docx"
    ></rich-inputfile>
    <div class="output" id="docsOutput">Only PDF and Word documents allowed</div>
    <div class="code-block">&lt;rich-inputfile
  label="Upload Document"
  allowed-extensions="pdf,doc,docx"
&gt;&lt;/rich-inputfile&gt;</div>
  </div>

  <div class="demo-section">
    <h2>Size Limit</h2>
    <p>Restrict maximum file size.</p>
    <rich-inputfile
      id="sized"
      label="Upload (max 1MB)"
      max-size="1048576"
    ></rich-inputfile>
    <div class="output" id="sizedOutput">Maximum 1MB file size</div>
    <div class="code-block">&lt;rich-inputfile
  label="Upload (max 1MB)"
  max-size="1048576"
&gt;&lt;/rich-inputfile&gt;</div>
  </div>

  <div class="demo-section">
    <h2>No Preview</h2>
    <p>Disable thumbnail preview for images.</p>
    <rich-inputfile
      id="nopreview"
      label="Upload File (no preview)"
      .showPreview="${false}"
    ></rich-inputfile>
    <div class="code-block">&lt;rich-inputfile
  label="Upload File (no preview)"
  show-preview="false"
&gt;&lt;/rich-inputfile&gt;</div>
  </div>

  <div class="demo-section">
    <h2>Disabled State</h2>
    <p>Disable the file input.</p>
    <rich-inputfile
      id="disabled"
      label="Upload (disabled)"
      disabled
    ></rich-inputfile>
    <div class="code-block">&lt;rich-inputfile label="Upload (disabled)" disabled&gt;&lt;/rich-inputfile&gt;</div>
  </div>

  <div class="demo-section">
    <h2>No Dropzone</h2>
    <p>Click-only mode without drag and drop.</p>
    <rich-inputfile
      id="nodrop"
      label="Click to Upload"
      .dropzone="${false}"
    ></rich-inputfile>
    <div class="code-block">&lt;rich-inputfile
  label="Click to Upload"
  dropzone="false"
&gt;&lt;/rich-inputfile&gt;</div>
  </div>

  <div class="demo-section">
    <h2>Custom Preview Size</h2>
    <p>Adjust the thumbnail preview size.</p>
    <rich-inputfile
      id="bigpreview"
      label="Large Preview"
      allowed-extensions="jpg,jpeg,png,gif"
      preview-size="100"
    ></rich-inputfile>
    <div class="code-block">&lt;rich-inputfile
  label="Large Preview"
  preview-size="100"
&gt;&lt;/rich-inputfile&gt;</div>
  </div>

  <div class="demo-section">
    <h2>Programmatic Control</h2>
    <p>Control the component via JavaScript.</p>
    <rich-inputfile id="programmatic" label="Controlled Input"></rich-inputfile>
    <div style="margin-top: 1rem;">
      <button onclick="clearFile()">Clear File</button>
      <button onclick="getFileInfo()">Get File Info</button>
    </div>
    <div class="output" id="programmaticOutput">Use buttons to interact</div>
    <div class="code-block">// Get file
const file = inputfile.getFile();

// Get as ArrayBuffer
const buffer = await inputfile.getFileArrayBuffer();

// Get as Data URL
const dataUrl = await inputfile.getFileDataURL();

// Clear file
inputfile.clear();</div>
  </div>
CSS (css)
:root {
  --bg: #0c0f14;
  --bg-elevated: #141923;
  --bg-panel: #171d28;
  --border: #262f3f;
  --text: #f4f6fb;
  --text-muted: #a7b0c2;
  --text-dim: #7d879b;
  --accent: #ff8a3d;
  --accent-strong: #ff6a00;
  --accent-soft: rgba(255, 138, 61, 0.16);
  --shadow: 0 20px 50px rgba(5, 8, 14, 0.45);
  --radius-lg: 22px;
  --radius-md: 14px;
  --radius-sm: 10px;
  --max-width: 1160px;
}

h1 { color: #1d1d1f; }
    .demo-section {
      background: white;
      padding: 2rem;
      border-radius: 12px;
      margin-bottom: 2rem;
      box-shadow: 0 2px 8px rgba(0,0,0,0.1);
    }
    h2 { margin-top: 0; color: #1d1d1f; }
    p { color: #86868b; line-height: 1.6; }
    a { color: #3b82f6; }
    .code-block {
      background: #1d1d1f;
      color: #f5f5f7;
      padding: 1rem;
      border-radius: 8px;
      font-family: monospace;
      font-size: 0.8rem;
      margin-top: 1rem;
      overflow-x: auto;
      white-space: pre;
    }
    .output {
      margin-top: 1rem;
      padding: 1rem;
      background: #f5f5f7;
      border-radius: 8px;
      font-family: monospace;
      font-size: 0.85rem;
    }
    rich-inputfile {
      margin-bottom: 1rem;
    }
  
    footer {
      text-align: center;
      margin-top: 3rem;
      padding-top: 2rem;
      border-top: 1px solid #d2d2d7;
      color: #86868b;
      font-size: 0.9rem;
    }
    footer a { color: #1d1d1f; text-decoration: none; }
    footer a:hover { text-decoration: underline; }
  


@import url('https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;600;700&display=swap');

:root {
  --bg: #f5f5f7;
  --bg-2: #ffffff;
  --bg-spot-1: #f8e9d0;
  --bg-spot-2: #e8eef8;
  --card: #ffffff;
  --text: #1d1d1f;
  --muted: #6b7280;
  --line: #e5e7eb;
  --accent: #ffb000;
  --accent-2: #00a7d6;
  --accent-soft: rgba(255, 176, 0, 0.18);
  --surface-1: #f3f4f6;
  --surface-2: #eef2f7;
  --code-bg: #111827;
  --code-text: #f9fafb;
  --panel-bg: rgba(255, 255, 255, 0.85);
  --overlay-bg: rgba(255, 255, 255, 0.98);
}

:root.dark {
  --bg: #0f1117;
  --bg-2: #151a26;
  --bg-spot-1: #1a2136;
  --bg-spot-2: #1d1b34;
  --card: #1c2233;
  --text: #f3f6ff;
  --muted: #b8c0d9;
  --line: #2b3247;
  --accent: #ffb000;
  --accent-2: #00d0ff;
  --accent-soft: rgba(255, 176, 0, 0.25);
  --surface-1: #0b0f1a;
  --surface-2: #263046;
  --code-bg: #0b0f1a;
  --code-text: #d6d9e6;
  --panel-bg: rgba(28, 34, 51, 0.8);
  --overlay-bg: rgba(15, 17, 23, 0.98);
}

* {
  box-sizing: border-box;
}

a {
  color: var(--accent);
}

h1 {
  color: var(--accent);
}

.demo-links {
  margin-top: 12px;
  display: flex;
  gap: 12px;
  justify-content: center;
  flex-wrap: wrap;
}

.demo-links a {
  border: 1px solid var(--line);
  border-radius: 999px;
  padding: 6px 12px;
  color: var(--muted);
  text-decoration: none;
  font-size: 0.85rem;
  transition: border-color 0.2s ease, color 0.2s ease;
}

.demo-links a:hover {
  color: var(--text);
  border-color: var(--accent-2);
}

.demo-theme-toggle {
  margin-top: 12px;
  display: flex;
  justify-content: center;
}

header {
  border-bottom: 1px solid var(--line);
}

.demo-card,
.section,
.demo-section,
.panel,
.card {
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: 16px;
  color: var(--text);
  box-shadow: 0 18px 36px rgba(6, 10, 24, 0.45);
}

.demo-card {
  display: flex;
  flex-direction: column;
  gap: 12px;
}

.demo-card .code-block {
  margin-top: auto;
}

.demo-grid,
.grid {
  align-items: stretch;
}

.demo-card h2,
.demo-card h3,
.section h2,
.demo-section h2,
.panel-header {
  color: var(--text);
}

.label,
.stat-label,
.category-title,
.subtitle,
.hint,
.note {
  color: var(--muted);
}

.info-item,
.capability,
.preference,
.option-group,
.output,
.current-url,
.event-log,
.result-card,
.log,
.stat {
  background: var(--surface-1);
  border: 1px solid var(--line);
  color: var(--text);
}

.info-item .label,
.capability .name {
  color: var(--muted);
}

.panel-header,
.options,
.topbar,
.top-links {
  background: var(--panel-bg);
  border-bottom: 1px solid var(--line);
  color: var(--text);
}

.subtitle,
.hint,
.note,
.demo-card p,
.section p,
.demo-section p {
  color: var(--muted);
}

.code-block,
pre,
code {
  background: var(--code-bg);
  color: var(--code-text);
  border-radius: 8px;
}

.value-display,
.output,
.result,
.demo-output {
  background: var(--surface-1);
  border: 1px solid var(--line);
  color: var(--text);
}

button {
  background: linear-gradient(120deg, var(--accent), #ff6a00);
  color: #111;
  border: none;
}

button:hover {
  filter: brightness(1.05);
}

input,
select,
textarea {
  background: var(--surface-1);
  color: var(--text);
  border: 1px solid var(--line);
}

footer {
  color: var(--muted);
}

footer a {
  color: var(--text);
}

arc-slider {
  --arc-slider-text-color: var(--text);
  --arc-slider-value-bg: var(--surface-1);
  --arc-slider-value-border: var(--line);
  --arc-slider-value-shadow: 0 6px 16px rgba(0, 0, 0, 0.35);
}

rich-select {
  --caller-background: var(--card);
  --caller-color: var(--text);
  --caller-border: 1px solid var(--line);
  --caller-hover-background: var(--surface-2);
  --caller-hover-color: var(--text);
  --caller-hover-border-color: var(--line);
  --caller-focus-border-color: var(--accent-2);
  --caller-focus-shadow: 0 0 0 3px rgba(0, 208, 255, 0.25);
  --caller-disabled-background: var(--surface-1);
  --caller-disabled-color: #6b7280;
  --caller-disabled-border-color: var(--line);
  --arrow-color: var(--muted);
  --selectOptions-background: var(--card);
  --selectOptions-border: 1px solid var(--line);
  --selectOptions-shadow: 0 10px 22px rgba(0, 0, 0, 0.45);
  --input-background: var(--surface-1);
  --input-border: 1px solid var(--line);
  --input-color: var(--text);
  --input-placeholder-color: #6b7280;
  --option-color: var(--text);
  --option-hover-background: var(--surface-2);
  --option-hover-color: var(--text);
  --option-active-background: var(--accent);
  --option-active-color: #111;
  --option-selected-background: var(--accent-soft);
  --option-selected-color: var(--text);
  --option-disabled-background: var(--surface-1);
  --option-disabled-color: #6b7280;
}

multi-select {
  --multi-select-bg: var(--card);
  --multi-select-border-color: var(--line);
  --multi-select-border-hover: var(--line);
  --multi-select-border-focus: var(--accent-2);
  --multi-select-text-color: var(--text);
  --multi-select-placeholder-color: #6b7280;
  --multi-select-arrow-color: var(--muted);
  --multi-select-dropdown-bg: var(--card);
  --multi-select-shadow: 0 10px 22px rgba(0, 0, 0, 0.45);
  --multi-select-option-hover-bg: var(--surface-2);
  --multi-select-option-selected-bg: var(--accent-soft);
}

tab-nav {
  --tab-bg: var(--card);
  --tab-border: var(--line);
  --tab-text: var(--muted);
  --tab-active-text: var(--text);
  --tab-hover-bg: var(--surface-2);
  --tab-active-border: var(--accent);
  --tab-disabled: #9ca3af;
}

slider-underline {
  --slider-track: var(--surface-2);
  --slider-fill: var(--accent);
  --slider-thumb: var(--accent);
  --slider-label-color: var(--text);
  --slider-tick-color: #9ca3af;
  --slider-tick-value-color: var(--muted);
}

header-nav {
  --header-bg: var(--card);
  --header-shadow: 0 12px 24px rgba(0, 0, 0, 0.2);
  --header-link-color: var(--text);
  --header-link-hover: var(--accent);
  --header-mobile-hover-bg: var(--surface-2);
}

calendar-inline {
  --calendar-bg: var(--card);
  --calendar-shadow: 0 12px 24px rgba(0, 0, 0, 0.2);
  --calendar-text: var(--text);
  --calendar-accent: var(--accent);
  --calendar-today: var(--accent-soft);
  --calendar-selected: var(--accent);
  --calendar-hover-bg: var(--surface-2);
  --calendar-muted: var(--muted);
  --calendar-muted-strong: #9ca3af;
  --calendar-other-month: #9ca3af;
  --calendar-disabled: #cbd5e1;
  --calendar-holiday: #ef4444;
  --calendar-holiday-selected: #111;
}

marked-calendar {
  --calendar-bg: var(--card);
  --calendar-border: var(--line);
  --calendar-title: var(--text);
  --calendar-muted: var(--muted);
  --calendar-surface: var(--surface-1);
  --calendar-accent: var(--accent);
  --calendar-accent-hover: #ff7a1a;
  --calendar-border-strong: var(--line);
  --calendar-contrast: #111;
  --calendar-nav-bg: var(--surface-1);
  --calendar-nav-hover: var(--surface-2);
}

radar-chart {
  --radar-bg: var(--card);
  --radar-grid-color: var(--line);
  --radar-axis-color: #94a3b8;
  --radar-label-color: var(--muted);
}

multi-carousel {
  --carousel-bg: var(--card);
  --carousel-arrow-bg: var(--surface-1);
  --carousel-arrow-color: var(--text);
  --carousel-arrow-hover-bg: var(--surface-2);
  --carousel-arrow-hover-color: var(--text);
  --carousel-nav-bg: var(--surface-1);
  --carousel-nav-color: var(--muted);
  --carousel-nav-hover: #9ca3af;
  --carousel-nav-active: var(--accent);
  --carousel-focus-color: var(--accent-2);
}

nav-list {
  --nav-list-bg: var(--card);
  --nav-list-border-color: var(--line);
  --nav-list-selected-border-color: var(--accent);
  --nav-list-selected-bg: var(--surface-2);
  --nav-list-hover-bg: var(--surface-2);
  --nav-list-selected-color: var(--text);
}

theme-toggle {
  --theme-toggle-bg: var(--card);
  --theme-toggle-icon-color: var(--muted);
  --theme-toggle-hover-bg: var(--surface-2);
  --theme-toggle-active-bg: var(--surface-1);
  --theme-toggle-active-color: var(--text);
  --theme-toggle-dark-bg: var(--card);
  --theme-toggle-dark-border: var(--line);
  --theme-toggle-dark-icon-color: var(--muted);
  --theme-toggle-dark-active-bg: var(--surface-1);
  --theme-toggle-dark-active-color: var(--text);
  --theme-toggle-dark-hover-bg: var(--surface-2);
}

qr-code {
  --qr-fg: #0f1117;
  --qr-bg: #f3f6ff;
}

click-clock {
  --clock-color: var(--text);
  --clock-bg: var(--card);
  --clock-muted-color: var(--muted);
}

historical-line {
  --title-color: var(--text);
  --border-color: var(--line);
  --year-bg: var(--surface-1);
}

circle-steps {
  --steps-muted: var(--muted);
  --steps-text: var(--text);
  --steps-pending: var(--surface-2);
}

rich-inputfile {
  --input-border: var(--line);
  --input-border-focus: var(--accent-2);
  --input-bg: var(--card);
  --input-label-color: var(--text);
  --input-hover-bg: var(--surface-2);
  --input-drag-bg: var(--accent-soft);
  --input-disabled-bg: var(--surface-1);
  --input-success-border: #22c55e;
  --input-success-bg: rgba(34, 197, 94, 0.12);
  --input-icon-color: #94a3b8;
  --input-text-color: var(--muted);
  --input-accent-color: var(--accent-2);
  --input-file-bg: var(--surface-1);
  --input-preview-bg: var(--surface-1);
  --input-file-name-color: var(--text);
  --input-file-size-color: var(--muted);
  --input-error-color: #ef4444;
  --input-hint-color: var(--muted);
}

data-card {
  --data-card-bg: var(--card);
  --data-card-border-color: var(--line);
  --data-card-title-color: var(--text);
  --data-card-desc-color: var(--muted);
  --data-card-info-bg: var(--overlay-bg);
  --data-card-info-close-bg: var(--surface-2);
  --data-card-info-close-color: var(--text);
  --data-card-info-close-hover-bg: var(--surface-1);
  --data-card-info-text: var(--text);
  --data-card-loading-color: var(--muted);
  --data-card-info-trigger-hover: var(--accent);
}

app-modal {
  --app-modal-bg: var(--card);
  --app-modal-body-color: var(--text);
  --app-modal-standalone-bg: rgba(255, 176, 0, 0.35);
  --app-modal-standalone-color: #111;
  --app-modal-standalone-hover-bg: rgba(255, 176, 0, 0.6);
}
JS (js)
document.querySelectorAll('.footer-year').forEach(el => el.textContent = new Date().getFullYear());
  

    import "https://esm.sh/@manufosela/rich-inputfile";

    // Basic
    const basic = document.getElementById('basic');
    const basicOutput = document.getElementById('basicOutput');
    basic.addEventListener('file-change', (e) => {
      basicOutput.textContent = `Selected: ${e.detail.name} (${(e.detail.size / 1024).toFixed(1)} KB)`;
    });
    basic.addEventListener('file-clear', () => {
      basicOutput.textContent = 'File cleared';
    });

    // Docs
    const docs = document.getElementById('docs');
    const docsOutput = document.getElementById('docsOutput');
    docs.addEventListener('file-change', (e) => {
      docsOutput.textContent = `Document: ${e.detail.name}`;
    });
    docs.addEventListener('file-error', (e) => {
      docsOutput.textContent = `Error: ${e.detail.message}`;
    });

    // Sized
    const sized = document.getElementById('sized');
    const sizedOutput = document.getElementById('sizedOutput');
    sized.addEventListener('file-change', (e) => {
      sizedOutput.textContent = `File: ${e.detail.name} (${(e.detail.size / 1024).toFixed(1)} KB)`;
    });
    sized.addEventListener('file-error', (e) => {
      sizedOutput.textContent = `Error: ${e.detail.message}`;
    });

    // No preview
    const nopreview = document.getElementById('nopreview');
    nopreview.showPreview = false;

    // No dropzone
    const nodrop = document.getElementById('nodrop');
    nodrop.dropzone = false;

    // Programmatic
    const programmatic = document.getElementById('programmatic');
    const programmaticOutput = document.getElementById('programmaticOutput');

    programmatic.addEventListener('file-change', (e) => {
      programmaticOutput.textContent = `Selected: ${e.detail.name}`;
    });

    window.clearFile = () => {
      programmatic.clear();
      programmaticOutput.textContent = 'File cleared programmatically';
    };

    window.getFileInfo = async () => {
      const file = programmatic.getFile();
      if (file) {
        programmaticOutput.textContent = `Name: ${file.name}, Type: ${file.type}, Size: ${file.size} bytes`;
      } else {
        programmaticOutput.textContent = 'No file selected';
      }
    };
  

  import '../../theme-toggle/src/theme-toggle.js';

  const root = document.documentElement;
  root.classList.add('dark');

  const toggle = document.querySelector('theme-toggle');
  if (toggle) {
    toggle.theme = root.classList.contains('dark') ? 'dark' : 'light';
    toggle.addEventListener('theme-changed', (event) => {
      const theme = event.detail?.theme;
      if (!theme) return;
      root.classList.toggle('dark', theme === 'dark');
    });
  }