Upload

Component

Interactive examples and API documentation

Avatar Upload
Click on the avatar to select an image. Shows loading state during upload simulation.
Upload
Upload
Code
<%= render Hakumi::Space::Component.new(size: :large, align: :center) do %>
  <%# Circular avatar upload %>
  <%= render Hakumi::Upload::Component.new(
    action: "#",
    name: "avatar",
    accept: "image/*",
    list_type: :avatar,
    avatar_shape: :circle,
    avatar_size: 80,
    avatar_icon: :user,
    max_size: 5.megabytes,
    show_upload_list: false
  ) %>

  <%# Square avatar upload %>
  <%= render Hakumi::Upload::Component.new(
    action: "#",
    name: "avatar",
    accept: "image/*",
    list_type: :avatar,
    avatar_shape: :square,
    avatar_size: 80,
    avatar_icon: :user,
    max_size: 5.megabytes,
    show_upload_list: false
  ) %>
<% end %>
Pictures Wall
Upload pictures to a wall grid. The upload button disappears when the limit (6) is reached.
  • landscape.jpg landscape.jpg
  • forest.jpg forest.jpg
  • Upload
Max 6 pictures. Upload button hides when limit is reached. Hover over images to view or delete.
Code
<%= render Hakumi::Upload::Component.new(
  action: "#",
  name: "pictures_wall",
  id: "pictures-wall-upload",
  list_type: :picture_card,
  accept: "image/*",
  multiple: true,
  max_count: 6,
  inline_trigger: true,
  default_file_list: [
    { uid: "1", name: "landscape.jpg", status: "done", url: "https://images.unsplash.com/photo-1501785888041-af3ef285b470?auto=format&fit=crop&w=300&q=80" },
    { uid: "2", name: "forest.jpg", status: "done", url: "https://images.unsplash.com/photo-1500530855697-b586d89ba3ee?auto=format&fit=crop&w=300&q=80" }
  ]
) %>

<div style="margin-top: 12px;">
  <%= render Hakumi::Typography::Text::Component.new(type: :secondary) do %>
    Max 6 pictures. Upload button hides when limit is reached. Hover over images to view or delete.
  <% end %>
</div>

<script>

    (() => {
  const init = () => {
    const upload = document.getElementById('pictures-wall-upload')
    if (!upload?.hakumiComponent?.api) return false

    const api = upload.hakumiComponent.api

    // Listen for file changes to trigger simulated upload
    upload.addEventListener('hakumi:upload:change', (e) => {
      const fileList = e.detail.fileList || []

      // Find files in 'ready' status and simulate upload
      fileList.forEach((file) => {
        if (file.status === 'ready') {
          api.simulateUpload(file.uid, { duration: 1500, success: true })
        }
      })
    })

    return true
  }

  if (init()) return

  const onReady = () => {
    if (init()) {
      document.removeEventListener('turbo:load', onReady)
      window.removeEventListener('load', onReady)
    }
  }

  document.addEventListener('turbo:load', onReady)
  window.addEventListener('load', onReady)
})()
</script>
Basic Upload
Use the upload component to select files and show progress in the file list.
Code
<%= render Hakumi::Upload::Component.new(
  action: "#",
  name: "file",
  list_type: :picture,
  accept: "image/*",
  max_size: 5.megabytes,
  max_count: 5
) do %>
  Select Images
<% end %>
Drag and Drop
Enable drag mode to accept files dropped onto the card.
Click or drag file to this area to upload
Support for a single or bulk upload.
Code
<%= render Hakumi::Upload::Component.new(
  action: "#",
  name: "file",
  drag: true,
  list_type: :picture,
  accept: "image/*",
  max_size: 5.megabytes
) %>
Picture List
Display thumbnails with the picture list type.
  • mountains.jpg mountains.jpg
  • forest.jpg forest.jpg
Code
<%= render Hakumi::Upload::Component.new(
  action: "#",
  name: "file",
  list_type: :picture,
  accept: "image/*",
  max_size: 5.megabytes,
  default_file_list: [
    { uid: "1", name: "mountains.jpg", status: "done", url: "https://images.unsplash.com/photo-1501785888041-af3ef285b470?auto=format&fit=crop&w=300&q=80" },
    { uid: "2", name: "forest.jpg", status: "done", url: "https://images.unsplash.com/photo-1500530855697-b586d89ba3ee?auto=format&fit=crop&w=300&q=80" }
  ]
) %>
Picture Card
Show files in a card grid for gallery-style uploads.
  • gallery-1.jpg gallery-1.jpg
  • gallery-2.jpg gallery-2.jpg
Code
<%= render Hakumi::Upload::Component.new(
  action: "#",
  name: "file",
  list_type: :picture_card,
  accept: "image/*",
  max_size: 5.megabytes,
  default_file_list: [
    { uid: "1", name: "gallery-1.jpg", status: "done", url: "https://images.unsplash.com/photo-1500530855697-b586d89ba3ee?auto=format&fit=crop&w=400&q=80" },
    { uid: "2", name: "gallery-2.jpg", status: "done", url: "https://images.unsplash.com/photo-1501785888041-af3ef285b470?auto=format&fit=crop&w=400&q=80" }
  ]
) %>
Disabled
Disable the component to prevent selection and upload actions.
  • portfolio.pdf
Code
<%= render Hakumi::Upload::Component.new(
  action: "#",
  name: "file",
  disabled: true,
  default_file_list: [
    { uid: "1", name: "portfolio.pdf", status: "done" }
  ]
) do %>
  Upload disabled
<% end %>
Simulated Upload
Use the simulation API to demo upload progress without a real server.

Upload component with simulated file operations:

Control the upload programmatically:

Code
<%= render Hakumi::Space::Component.new(size: :large, direction: :vertical, align: :start) do %>
  <%= render Hakumi::Space::Component.new(size: :middle, direction: :vertical, align: :start) do %>
    <p style="margin-bottom: 8px;">Upload component with simulated file operations:</p>
    <%= render Hakumi::Upload::Component.new(
      action: "#",
      name: "simulated_files",
      id: "simulated-upload",
      list_type: :picture,
      accept: "image/*",
      auto_upload: false
    ) do %>
      Select Images
    <% end %>
  <% end %>

  <%= render Hakumi::Space::Component.new(size: :middle, direction: :vertical, align: :start) do %>
    <p style="margin-bottom: 8px;">Control the upload programmatically:</p>
    <%= render Hakumi::Space::Component.new(direction: :horizontal, size: :small) do %>
      <%= render Hakumi::Button::Component.new(
        id: "add-file-btn",
        type: :default
      ) do %>
        Add File
      <% end %>

      <%= render Hakumi::Button::Component.new(
        id: "simulate-btn",
        type: :primary
      ) do %>
        Simulate Upload
      <% end %>

      <%= render Hakumi::Button::Component.new(
        id: "simulate-error-btn",
        type: :default,
        danger: true
      ) do %>
        Simulate Error
      <% end %>
    <% end %>
  <% end %>
<% end %>

<script>
document.addEventListener('DOMContentLoaded', function() {
  const upload = document.getElementById('simulated-upload')
  const addBtn = document.getElementById('add-file-btn')
  const simulateBtn = document.getElementById('simulate-btn')
  const errorBtn = document.getElementById('simulate-error-btn')

  let fileCounter = 1

  // Sample image URLs for simulated files
  const sampleImages = [
    'https://images.unsplash.com/photo-1501785888041-af3ef285b470?auto=format&fit=crop&w=300&q=80',
    'https://images.unsplash.com/photo-1500530855697-b586d89ba3ee?auto=format&fit=crop&w=300&q=80',
    'https://images.unsplash.com/photo-1469474968028-56623f02e42e?auto=format&fit=crop&w=300&q=80'
  ]

  addBtn.addEventListener('click', function() {
    const api = upload.hakumiComponent?.api
    if (!api) return

    // Add a simulated file record with preview image
    const file = api.addFile({
      name: `image-${fileCounter}.jpg`,
      status: 'ready',
      size: Math.floor(Math.random() * 1000000),
      url: sampleImages[(fileCounter - 1) % sampleImages.length]
    })
    fileCounter++

    if (!file) {
      console.log('Max count reached or error adding file')
    }
  })

  simulateBtn.addEventListener('click', function() {
    const api = upload.hakumiComponent?.api
    if (!api) return

    const fileList = api.getFileList()
    const readyFiles = fileList.filter(f => f.status === 'ready')

    if (readyFiles.length === 0) {
      // Add a file first if none ready
      const file = api.addFile({
        name: `upload-${fileCounter}.jpg`,
        status: 'ready',
        url: sampleImages[(fileCounter - 1) % sampleImages.length]
      })
      fileCounter++
      if (file) {
        api.simulateUpload(file.uid, { duration: 2000, success: true })
      }
    } else {
      // Simulate upload for first ready file
      api.simulateUpload(readyFiles[0].uid, { duration: 2000, success: true })
    }
  })

  errorBtn.addEventListener('click', function() {
    const api = upload.hakumiComponent?.api
    if (!api) return

    // Add a file and simulate failed upload
    const file = api.addFile({
      name: `failed-upload-${fileCounter}.jpg`,
      status: 'ready',
      url: sampleImages[(fileCounter - 1) % sampleImages.length]
    })
    fileCounter++

    if (file) {
      api.simulateUpload(file.uid, {
        duration: 1500,
        success: false,
        error: 'Server rejected the file'
      })
    }
  })

  // Listen to events
  upload.addEventListener('hakumi:upload:success', function(e) {
    console.log('Upload success:', e.detail.file?.name)
  })

  upload.addEventListener('hakumi:upload:error', function(e) {
    console.log('Upload error:', e.detail.file?.name, e.detail.error)
  })
})
</script>

Upload API

Prop Type Default Description
action String - Upload endpoint URL (required).
name String - Form field name for file payloads (required).
accept String - Accepted file types (e.g., 'image/*', '.pdf,.doc'). Validated client-side.
multiple Boolean false Allow selecting multiple files.
max_count Integer - Maximum number of files in the list.
max_size Integer - Maximum file size in bytes.
disabled Boolean false Disable interactions.
list_type Symbol :text Rendering style: :text, :picture, :picture_card.
show_upload_list Boolean true Show the file list.
drag Boolean false Enable drag-and-drop zone.
headers Hash - Extra headers for the upload request (security headers blocked).
data Hash - Extra form data sent with uploads.
default_file_list Array [] Initial list of files with keys: uid, name, status, url, percent.
with_credentials Boolean false Send cookies with cross-origin requests.
auto_upload Boolean true Automatically upload files when selected. Set to false for form integration or manual control.
inline_trigger Boolean false Place upload trigger inside the file list grid (Pictures Wall pattern). Hides when max_count is reached.

Upload JavaScript API (element.hakumiComponent.api)

Prop Type Default Description
upload(file) Function - Upload a specific file (by uid, record, or File object).
uploadAll() Function - Upload all files with status 'ready' or 'error'.
remove(file) Function - Remove a file from the list (by uid or record).
removeAll() Function - Remove all files from the list.
getFileList() Function - Get a copy of the current file list array.
setFileList(list) Function - Replace the file list with a new array.
abort(file) Function - Abort an in-progress upload for a specific file.
abortAll() Function - Abort all in-progress uploads.

Simulation API (element.hakumiComponent.api)

Prop Type Default Description
addFile(fileData) Function - Add a file record programmatically. Returns the created record or null if max_count reached.
setProgress(uid, percent) Function - Set progress (0-100) on a specific file by uid.
setStatus(uid, status, error?) Function - Set status on a file. Valid: ready, uploading, done, error, aborted.
simulateUpload(uid, options) Function - Animate upload progress. Options: { duration: ms, success: bool, error: string }. Returns Promise.

Preview API (element.hakumiComponent.api)

Prop Type Default Description
openPreview(file) Function - Open the gallery preview for a specific file (by uid or record). Shows all uploaded images with navigation.
getPreviewItems() Function - Get array of previewable items with src, alt, and uid. Only returns files with 'done' status and valid URLs.

Events

Prop Type Default Description
hakumi:upload:change CustomEvent - Fired when the file list changes. Detail: { file, fileList }.
hakumi:upload:progress CustomEvent - Fired during upload progress. Detail: { file, fileList, event }.
hakumi:upload:success CustomEvent - Fired when a file uploads successfully. Detail: { file, fileList }.
hakumi:upload:error CustomEvent - Fired when an upload fails. Detail: { file, fileList, error }.

Security Features

Prop Type Default Description
CSRF Token Automatic - Automatically includes Rails CSRF token from meta tag.
File Type Validation Client-side - Validates files against accept attribute before upload.
URL Sanitization Automatic - Only allows http, https, and data:image/* URLs for thumbnails.
File Name Sanitization Automatic - Removes potentially dangerous characters from file names.
Header Restrictions Automatic - Blocks setting cookie, host, origin, referer headers.