x
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<div class="flex items-center gap" data-controller="upload-preview" data-upload-preview-default-image-value="https://csszero.lazaronixon.com/assets/default-picture-956f249d.webp"> <label class="btn btn--icon"> <input type="file" name="avatar" id="avatar" class="sr-only" accept="image/*" data-upload-preview-target="input" data-action="upload-preview#previewImage" /> <img aria-hidden="true" src="/assets/camera-84102100.svg" width="20" height="20" /> <span class="sr-only">Upload avatar</span> </label> <div class="avatar" style="--avatar-size: 100px;"> <img alt="Avatar" data-upload-preview-target="image" src="/assets/cartoon-93de1535.jpg" width="48" height="48" /> </div> <label class="btn btn--icon btn--negative" data-action="click->upload-preview#clear"> <input type="hidden" name="remove_avatar" id="remove_avatar" value="false" data-upload-preview-target="removeInput" autocomplete="off" /> <img aria-hidden="true" src="/assets/minus-b71b2ac1.svg" width="20" height="20" /> <span class="sr-only">Remove avatar</span> </label></div>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<%= tag.div class: "flex items-center gap", data: { controller: "upload-preview", upload_preview_default_image_value: asset_url("default-picture.webp") } do %> <label class="btn btn--icon"> <%= file_field_tag :avatar, class: "sr-only", accept: "image/*", data: { upload_preview_target: "input", action: "upload-preview#previewImage" } %> <%= image_tag "camera.svg", size: 20, aria: { hidden: true } %> <span class="sr-only">Upload avatar</span> </label> <div class="avatar" style="--avatar-size: 100px;"> <%= image_tag "cartoon.jpg", size: 48, alt: "Avatar", data: { upload_preview_target: "image" } %> </div> <label class="btn btn--icon btn--negative" data-action="click->upload-preview#clear"> <%= hidden_field_tag "remove_avatar", false, data: { upload_preview_target: "removeInput" } %> <%= image_tag "minus.svg", size: 20, aria: { hidden: true } %> <span class="sr-only">Remove avatar</span> </label><% end %>
CSS is not required or multiple files are needed, check the notes.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import { Controller } from "@hotwired/stimulus"export default class extends Controller { static targets = [ "image", "input", "removeInput" ] static values = { defaultImage: String } previewImage() { const selectedFile = this.inputTarget.files[0] if (selectedFile) { this.imageTarget.src = URL.createObjectURL(selectedFile); this.imageTarget.onload = () => { URL.revokeObjectURL(this.imageTarget.src) } this.removeInputTarget.value = false } } clear() { this.imageTarget.src = this.defaultImageValue this.removeInputTarget.value = true }}
Example
You can remove the attachment in the same request using the hidden input value.
user.avatar.purge if params[:remove_avatar] == 'true'