«image» Component Type
📖 Table of Contents
Introduction
The image component type extends the file type to render the field in place on a native <img> element — the picture itself is the field. It inherits the file value contract (a self-describing data-URL string by default, or a structured object with "format":"json"), its acquisition machinery (picker, drag & drop, paste), its list integration and its download action, and adds:
- On-screen display: the stored bytes are shown directly as the image; while empty a placeholder is displayed.
- Decoded-image validation: acquired files are actually decoded, so a broken or non-image file is rejected silently, like an unaccepted file.
- Best-effort processing: optional
image_resize/image_maxSize/image_formatconversion on acquisition, with configurableimage_enforcebehaviour when a requirement cannot be met or verified. - Editable file name: on
<figure>wrappers and in galleries, afigcaption contenteditablemirrors and edits the storedname.
Images are embedded-only, exactly like file: the value always holds the bytes (as base64 internally), never an external URL.
Inference: bare
<img data-smark>elements are automatically inferred asimagebyinferType()— no"type"declaration is needed. An explicit{"type":"image"}on an<input type="image">is an error (IMAGE_TYPE_ON_INPUT): that tag is a form submit-piece, not an image field. Use an<img>or a singleton container instead.
Declaring an Image Field
Real Field
The simplest, most direct form turns any <img> element into the field:
<img
data-smark='{"name":"photo"}'
width="320" height="200"
alt="Profile photo"
>
Specify the size. The field only manages src (and the title tooltip); it never touches width, height, class or style. Reserve the layout space in the markup (width/height attributes shown above, sizes, or CSS sizing like max-width:100%; height:auto) so the page does not shift when a different-sized image is picked or imported.
<div id="myForm">
<p>
<label data-smark="label">Profile photo:</label>
</p>
<img
data-smark='{"name":"photo"}'
width="320" height="200"
class="photo"
alt="Profile photo"
>
</div>
#myForm .photo {
display: block;
max-width: 100%;
border: 1px dashed #aaa;
border-radius: .5rem;
object-fit: cover;
}
const myForm = new SmarkForm(document.getElementById("myForm"), {
"value": {
"photo": "data:image/png;name=photo.png;size=68;lastModified=0;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNk+A8AAQUBAScY42YAAAAASUVORK5CYII="
}
});
👉 Inferred type: the field is a bare <img data-smark> — the image type is inferred from the tag name, no explicit "type" needed.
👉 Click to pick: click the image (or press Space / Shift+Space) to open the OS file picker. Delete / Backspace clears the value.
👉 Drag & paste: drop an image file onto the field or paste a screenshot — both set the value.
👉 Placeholder: while empty, the field shows a generated chessboard placeholder. Set "placeholder":"…" to a URL/data-URL of your own, or "placeholder":false to leave src empty.
👉 Broken images never land: an acquired file that cannot be decoded is rejected silently — the value stays as it was.
Try it! Load the demo value, then click the image and pick a different file from your disk.
Every example in this section comes with many of the following tabs:
- HTML: HTML source code of the example.
- CSS: CSS applied (if any).
- JS: JavaScript source code of the example.
- Preview: Live, sandboxed rendering of the example — fully isolated from the page styles.
- Notes: Additional notes and insights for better understanding. Don't miss it‼️
✨ In the Preview tab, a JSON playground editor is available with handy buttons:
⬇️ Exportto export the form data to the JSON playground editor.⬆️ Importto import data from the JSON playground editor into the form.♻️ Resetin forms prefilled with sample data, resets the form to its default values.❌ Clearto clear the whole form.
💡 The JSON playground editor is part of the SmarkForm form itself — it is just omitted from the code snippets to keep the examples focused on what matters.
🛠️ Between the tab labels and the content there is always an edit toolbar:
✏️ Edit— activates edit mode: each source tab turns into a syntax-highlighted code editor (powered by Ace) pre-filled with the full, merged source. Changes are sandboxed — the original example is not affected.📋 Include playground editor— (only visible in edit mode) controls whether the JSON playground editor is included in the preview. When toggled, the HTML and JS editors update instantly so you can see exactly what code is needed to add or remove it.▶️ Run— (only visible in edit mode) re-renders the Preview from the current editor contents and switches to the Preview tab.
The Singleton Pattern
Wrapping the field in another element turns the whole wrap area into the field — the Singleton Pattern — and applies drop, paste and click handlers over the entire container. A <figure> wrapper additionally turns a figcaption contenteditable into the editable file name:
<div id="myForm">
<figure
class="drop-zone"
data-smark='{"type":"image","name":"photo","image_maxSize":[300,300]}'
>
<img data-smark width="300" height="200" alt="Photo">
<figcaption class="caption" contenteditable>(edit file name)</figcaption>
<button data-smark='{"action":"download"}'>Download</button>
</figure>
</div>
#myForm .drop-zone {
border: 2px dashed #999;
border-radius: .5rem;
padding: 1rem;
max-width: 420px;
text-align: center;
}
#myForm .drop-zone img {
display: block;
max-width: 100%;
margin: 0 auto;
border-radius: .25rem;
}
#myForm .drop-zone figcaption {
font-size: .85em;
font-style: italic;
color: #666;
margin: .5rem 0;
padding: .25em;
}
#myForm .drop-zone figcaption:focus {
outline: 1px dashed #999;
}
const myForm = new SmarkForm(document.getElementById("myForm"), {
"value": {
"photo": "data:image/png;name=photo.png;size=68;lastModified=0;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNk+A8AAQUBAScY42YAAAAASUVORK5CYII="
}
});
👉 The whole dashed box is the field. Click anywhere on it (or press Shift+Space) to open the picker; drop or paste images onto the box.
👉 Editable name: the caption mirrors the stored file name and edits it — a non-empty edited caption wins on export and download, exactly like the visible name field on a file. Typing here never triggers the picker.
👉 image_maxSize [300,300]: images picked or dropped are downscaled to fit within 300×300, preserving the aspect ratio (picked images only — imports pass through untouched).
👉 Download: the Download button gets the stored bytes back as a real browser download of the current bytes/name.
👉 Singleton rules: the container must hold exactly one inner field (NOT_A_SINGLETON otherwise), and it must be an <img> (IMAGE_MISSING_IMG otherwise). Options declared on the container (e.g. image_maxSize, accept, format) are inherited by the inner field.
Try it! Load the demo value, then edit the caption and download — the file arrives with the caption’s name.
Every example in this section comes with many of the following tabs:
- HTML: HTML source code of the example.
- CSS: CSS applied (if any).
- JS: JavaScript source code of the example.
- Preview: Live, sandboxed rendering of the example — fully isolated from the page styles.
- Notes: Additional notes and insights for better understanding. Don't miss it‼️
✨ In the Preview tab, a JSON playground editor is available with handy buttons:
⬇️ Exportto export the form data to the JSON playground editor.⬆️ Importto import data from the JSON playground editor into the form.♻️ Resetin forms prefilled with sample data, resets the form to its default values.❌ Clearto clear the whole form.
💡 The JSON playground editor is part of the SmarkForm form itself — it is just omitted from the code snippets to keep the examples focused on what matters.
🛠️ Between the tab labels and the content there is always an edit toolbar:
✏️ Edit— activates edit mode: each source tab turns into a syntax-highlighted code editor (powered by Ace) pre-filled with the full, merged source. Changes are sandboxed — the original example is not affected.📋 Include playground editor— (only visible in edit mode) controls whether the JSON playground editor is included in the preview. When toggled, the HTML and JS editors update instantly so you can see exactly what code is needed to add or remove it.▶️ Run— (only visible in edit mode) re-renders the Preview from the current editor contents and switches to the Preview tab.
The Placeholder
While the field is empty its src shows the default placeholder: a generated black-and-white chessboard (an inline SVG data URL — deterministic and offline, no network or asset needed).
"placeholder":"…"— any URL or data URL replaces the chessboard."placeholder":false— leavesrcempty (the browser shows its own broken/ empty-image glyph).
The field never touches the authored alt text; keep it for accessibility. If the browser later fails to render a loaded value (e.g. an exotic format), the field falls back to the placeholder while keeping the value intact for export, and logs a single console.warn.
Acquiring Images
- Click /
Space/Shift+Spaceopen the OS picker (a real user gesture is always required; the hidden picker is soft-clicked inside the handler).Shift+Spaceyields to the<details>folding convention when that wins. - Drop and paste replace the value; both are filtered by
accept(default"image/*"). Delete/Backspaceclear the value (smark_image_clearOnDelete, on by default) — but never while the focus is inside an editable caption, where those keys edit text.
Each of the three acquisition paths can be disabled independently with smark_image_open / smark_image_drop / smark_image_paste (all default to true; the smark_file_* names are honored as fallbacks).
Whatever the route, an acquired file must decode as an image (smark_image_validate, on by default); a candidate that does not is rejected silently. Only after that gate are image_resize/image_maxSize/ image_format processed and image_enforce consulted below. One change event fires whenever the value is set, replaced or cleared.
Resizing and Converting
The three processing options act on decoded, newly acquired candidates. Imports (and re-imports of an already-acquired value) pass through untouched.
| Option | Shape | Effect |
|---|---|---|
image_resize | [w,h], {width,height} or a bare number (= square) | Resize to an exact target box (stretches) |
image_maxSize | same as image_resize | Downscale-only cap: fit within the box preserving aspect ratio |
image_format | "jpeg"·"jpg"·"png"·"webp"·"avif" (canonical) | Convert the encoded format on acquisition |
Conversion runs through a canvas at ~92% JPEG quality. When the format changes the file name is re-extended (photo.png → photo.jpg); when a source has no name it defaults to image.<ext>. image_format and image_jpg map to the same MIME type; jpeg is canonical. Unverifiable and unsupported outcomes are reported through image_enforce below.
<div id="myForm">
<p>
<label data-smark="label">Square avatar (auto-resized & converted):</label>
</p>
<img
data-smark='{"name":"avatar","image_resize":[200,200],"image_format":"webp"}'
width="200" height="200"
class="avatar"
alt="Square avatar"
>
</div>
#myForm .avatar {
display: block;
max-width: 100%;
border: 1px dashed #aaa;
border-radius: .5rem;
object-fit: cover;
}
const myForm = new SmarkForm(document.getElementById("myForm"), {
"value": {
"avatar": "data:image/png;name=avatar.png;size=68;lastModified=0;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNk+A8AAQUBAScY42YAAAAASUVORK5CYII="
}
});
👉 Pick a photo and it is stretched into a 200×200 square and, where the browser can encode it, converted to WebP.
👉 Not every engine can encode every format. When the requested conversion is unavailable (e.g. WebP on older engines), the original bytes are kept and — because image_enforce defaults to "warn" — an in-page toast and a bubbling smark:imageNotice event explain why.
👉 Imports are untouched: demo values and import()ed data are displayed as-is; the resize/format pipeline only applies to newly picked/dropped/ pasted files.
Try it! Load the demo value, then pick a photo from your disk and watch it become a 200×200 square.
Every example in this section comes with many of the following tabs:
- HTML: HTML source code of the example.
- CSS: CSS applied (if any).
- JS: JavaScript source code of the example.
- Preview: Live, sandboxed rendering of the example — fully isolated from the page styles.
- Notes: Additional notes and insights for better understanding. Don't miss it‼️
✨ In the Preview tab, a JSON playground editor is available with handy buttons:
⬇️ Exportto export the form data to the JSON playground editor.⬆️ Importto import data from the JSON playground editor into the form.♻️ Resetin forms prefilled with sample data, resets the form to its default values.❌ Clearto clear the whole form.
💡 The JSON playground editor is part of the SmarkForm form itself — it is just omitted from the code snippets to keep the examples focused on what matters.
🛠️ Between the tab labels and the content there is always an edit toolbar:
✏️ Edit— activates edit mode: each source tab turns into a syntax-highlighted code editor (powered by Ace) pre-filled with the full, merged source. Changes are sandboxed — the original example is not affected.📋 Include playground editor— (only visible in edit mode) controls whether the JSON playground editor is included in the preview. When toggled, the HTML and JS editors update instantly so you can see exactly what code is needed to add or remove it.▶️ Run— (only visible in edit mode) re-renders the Preview from the current editor contents and switches to the Preview tab.
Validation and image_enforce
When a requirement (image_resize, image_maxSize or image_format) cannot be met (outcome B — known non-conforming conversion unavailable) or cannot be verified (outcome C — e.g. decode was skipped, so dimensions or the real encoded format are unknown), the field reports through image_enforce ("strict" | "hard" | "warn" | "ignore", default "warn"):
| Outcome | strict | hard | warn (default) | ignore |
|---|---|---|---|---|
| B — unmet, conversion unavailable | reject | reject | warn | accept silently |
| C — cannot be verified | reject | accept | accept | accept |
When the policy emits a notice, the field:
- dispatches a bubbling
smark:imageNoticeCustomEventon itstargetNodewithdetail: {kind, code, message, mode, name, requirement}(kind: "warning"|"rejection", codes likeIMAGE_FORMAT_UNSUPPORTED,IMAGE_DIMENSIONS_UNKNOWN); and - unless a handler called
preventDefault(), shows an in-page toast (role="status", auto-dismissed) with the reason.
window.alert()/confirm() are deliberately avoided: they are silently blocked inside sandboxed iframes. The event fires either way, so listeners never miss a notification. A rejection leaves the field value unchanged.
// Replace the toast with your own UX:
field.targetNode.addEventListener('smark:imageNotice', (ev) => {
ev.preventDefault(); // suppress the default toast
myCustomNotice(ev.detail.message, ev.detail.kind);
});
Images in Lists (Galleries)
Declaring a list whose item type is "image" renders each item as a thumbnail — the list becomes a gallery:
<ul data-smark='{"type":"list","name":"gallery","of":"image","min_items":0,"max_items":5}'>
<li data-smark='{"role":"empty_list"}'>(Drop images here…)</li>
<li data-smark='{"type":"image"}'>
<img data-smark width="120" height="120" alt="Gallery item">
<figcaption contenteditable></figcaption>
</li>
</ul>
- Preview: each item’s
<img>shows the file’s data URL (author controls sizing/presentation; reserve space withwidth/heightto avoid CLS). - Batch add:
addItemuses the inherited multi-fileacquire()picker, extended to decode-filter and size/format-process every selection (§image_*options on the list apply to the whole batch). - OS drop: dropping files anywhere on the list appends items (a drop on an existing item appends rather than replaces it — the item’s own drop is suppressed inside an
of:"image"list). Paste stays item-scoped. - Per-item caption: the optional
figcaption contenteditableis each thumbnail’s editable file name, exactly like the<figure>singleton. - Limits:
max_itemsoverflow is confirmed viawindow.confirm, as forfile.
The list imports/exports a flat array of data URLs (or JSON objects with "format":"json"), one entry per image.
<div id="myForm">
<button data-smark='{"action":"addItem","context":"gallery"}' title="Add images">➕ Add images</button>
<ul data-smark='{"type":"list","name":"gallery","of":"image","min_items":0,"max_items":5}'>
<li data-smark='{"role":"empty_list"}'>(Drop images here…)</li>
<li data-smark='{"type":"image"}'>
<img data-smark width="120" height="120" alt="Gallery item">
<figcaption contenteditable></figcaption>
</li>
</ul>
</div>
#myForm ul {
list-style: none;
padding-left: 0;
display: flex;
flex-wrap: wrap;
gap: .75rem;
}
#myForm li {
margin: 0;
}
#myForm li img {
display: block;
border: 1px dashed #aaa;
border-radius: .5rem;
object-fit: cover;
}
#myForm li figcaption {
font-size: .8em;
text-align: center;
color: #666;
max-width: 120px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
const myForm = new SmarkForm(document.getElementById("myForm"), {
"value": {
"gallery": [
"data:image/png;name=a.png;size=68;lastModified=0;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNk+A8AAQUBAScY42YAAAAASUVORK5CYII=",
"data:image/png;name=b.png;size=68;lastModified=0;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNk+A8AAQUBAScY42YAAAAASUVORK5CYII="
]
}
});
👉 Array export: each image becomes one entry of a flat array of data URLs.
👉 Multi-pick: click ➕ Add images — one picker lets you select several files at once; a cancelled dialog leaves the list untouched.
👉 Drop: drag images from your OS onto the list; each is appended as its own thumbnail. Dropping on an existing thumbnail appends too.
👉 Captions: each thumbnail’s caption mirrors its file name and is editable — a non-empty edited caption wins on that item’s export.
👉 Limit (5): when more images arrive than max_items allows you are asked to confirm before adding only the first fitting files.
Try it! Load the demo value (two images) and then drop another onto any of them.
Every example in this section comes with many of the following tabs:
- HTML: HTML source code of the example.
- CSS: CSS applied (if any).
- JS: JavaScript source code of the example.
- Preview: Live, sandboxed rendering of the example — fully isolated from the page styles.
- Notes: Additional notes and insights for better understanding. Don't miss it‼️
✨ In the Preview tab, a JSON playground editor is available with handy buttons:
⬇️ Exportto export the form data to the JSON playground editor.⬆️ Importto import data from the JSON playground editor into the form.♻️ Resetin forms prefilled with sample data, resets the form to its default values.❌ Clearto clear the whole form.
💡 The JSON playground editor is part of the SmarkForm form itself — it is just omitted from the code snippets to keep the examples focused on what matters.
🛠️ Between the tab labels and the content there is always an edit toolbar:
✏️ Edit— activates edit mode: each source tab turns into a syntax-highlighted code editor (powered by Ace) pre-filled with the full, merged source. Changes are sandboxed — the original example is not affected.📋 Include playground editor— (only visible in edit mode) controls whether the JSON playground editor is included in the preview. When toggled, the HTML and JS editors update instantly so you can see exactly what code is needed to add or remove it.▶️ Run— (only visible in edit mode) re-renders the Preview from the current editor contents and switches to the Preview tab.
Downloading an Image
The download action works exactly as for file — a real browser download of the stored bytes:
<button data-smark='{"action":"download","context":"photo"}'>Download</button>
On an empty field it is a no-op returning null. Name precedence: filename trigger option > edited caption (when present) > stored name. Because a transient user gesture is needed for reliability, wire it to a trigger button as shown (and seen in the Singleton example above).
Importing and Exporting Data
The value contract is identical to file:
- Empty state →
null(and the placeholder shows). - Default raw format → self-describing data-URL string:
data:image/png;name=photo.png;size=123456;lastModified=1690000000000;base64,…. "format":"json"→{name, type, size, lastModified, data}object with the payload perencoding("base64"/"base64url"/"hex").- Import accepts a data-URL string, a (partial) object, a bare payload string or a JSON string of any of those;
sizeis always recomputed. - Imported values are normalized and displayed immediately;
import()does not decode-validate (that stays on the acquisition path). - No
width/heightmetadata is stored or exported — the image is a field value; layout sizing is the author’s.
The interior state is a normalized file object ({name, type, size, lastModified, data} with data always base64) and carries no image-specific fields.
Limitations
- The OS picker cannot be opened programmatically — acquisition always requires a real user gesture (click / Space / drop / paste).
- Resize/format conversion handles only the first frame of animated images; full-frame animation is out of scope.
<picture>wrappers act only as a generic singleton container; responsivesrcset/sizesmulti-source values are a different feature, not managed.- MIME metadata is best-effort (past screenshots often lack it): an unknown format is the “cannot verify” outcome (C) of
image_enforce, not a rejection.
See also:
file— the underlying type and value contract; the Singleton Pattern and Files in Lists).