«video» Component Type
Table of Contents
Overview
The video type extends file and keeps its embedded file value contract, picker/drop/paste acquisition, import/export, singleton, list, and download behavior.
<video data-smark='{"type":"video","name":"clip"}'
width="640" height="360" controls preload="metadata"></video>
Bare <video data-smark> elements infer type: "video" automatically. Reserve the media box with width/height attributes or CSS aspect-ratio to avoid layout shift when a different clip is loaded.
Try It
These examples use small locally bundled clips so they work without a remote media host. You can replace them with your own videos in the preview. The playground’s Import button can also be used with an exported embedded value.
<div id="myForm">
<strong>Clip</strong>
<video data-smark='{"name":"clip"}' width="320" height="180" preload="metadata"></video>
<button data-smark='{"action":"pick","context":"clip"}'>Choose or replace video</button>
<button data-smark='{"action":"download","context":"clip"}'>Download current video</button>
</div>
const myForm = new SmarkForm(document.getElementById("myForm"), {
"value": {"clip": "/assets/video_cat_mixkit.mp4"}
});
👉 Try it! This first example omits native controls, so clicking the preview is the upload/replace affordance. Choose a local H.264/AAC MP4, WebM, or another format your browser supports, then use Download current video to retrieve the embedded value.
👉 The example starts with a small locally bundled Mixkit clip. The URL is fetched and embedded by demoValue; replace it with your own video to try the acquisition flow.
The bundled demo sources and license links are listed in video-demo-attribution.md. Thanks to the Mixkit contributors for these freely available demo clips.
Large export warning: video values are embedded as base64. Exporting a large clip into the playground textarea serializes the entire payload and can temporarily freeze the browser. This is a limitation of displaying a large JSON/data-URL value in a textarea, not a media playback requirement. Production applications should avoid rendering multi-megabyte exports in a textarea and should submit/store the value through an appropriate binary or server-side path.
Singleton With An Editable Name
<div id="myForm">
<figure class="video-card" data-smark='{"type":"video","name":"clip"}'>
<video data-smark width="320" height="180" controls></video>
<figcaption data-smark='{"action":"rename"}'>edit filename</figcaption>
<button data-smark='{"action":"pick"}'>Choose or replace video</button>
<button data-smark='{"action":"download"}'>Download</button>
</figure>
</div>
#myForm .video-card { max-width: 360px; padding: 1rem; border: 2px dashed #999; text-align: center; background: #fafafa; border-radius: .5rem; }
#myForm video { display: block; width: 100%; height: auto; }
#myForm figcaption { margin: .75rem 0; padding: .25rem; }
#myForm button { margin: .25rem; }
const myForm = new SmarkForm(document.getElementById("myForm"), {
"value": {"clip": "/assets/video_dog_mixkit.mp4"}
});
👉 The wrapper accepts drops and pastes, while the caption edits the exported filename. Upload a clip, replace edit filename, then use Download.
👉 Native controls supplies playback. Clicking the video is intentionally reserved for native playback; use the explicit Choose or replace video button to upload another clip. SmarkForm keeps the bytes unchanged and does not generate a thumbnail or modal player.
Video List
<div id="myForm">
<ul data-smark='{"type":"list","name":"clips","of":"video","min_items":0,"max_items":4}'>
<li>
<video data-smark width="240" height="135"></video>
<button data-smark='{"action":"removeItem"}'>Remove</button>
</li>
</ul>
<button data-smark='{"action":"addItem","context":"clips"}'>Add video</button>
</div>
const myForm = new SmarkForm(document.getElementById("myForm"), {
"value": {"clips": [
"/assets/video_cat_mixkit.mp4",
"/assets/video_dog_mixkit.mp4"
]}
});
👉 Add clips with the list picker or drop files onto the list. Video-capable lists append OS drops instead of replacing an existing item.
Form Items With Related Metadata
For richer records, make each list item a form. The list supplies of: "form", so the item wrapper does not need to repeat its type. Each item can then contain the video plus title, notes, or any other related fields.
<div id="myForm">
<ul data-smark='{"type":"list","name":"clips","of":"form","min_items":0,"max_items":4}'>
<li>
<video data-smark='{"name":"video"}' width="240" height="135"></video>
<input data-smark='{"name":"title"}' placeholder="Title">
<textarea data-smark='{"name":"notes"}' rows="2" placeholder="Notes"></textarea>
<button data-smark='{"action":"download"}'>Download video</button>
<button data-smark='{"action":"removeItem"}' title="Remove clip" aria-label="Remove clip">✖️</button>
</li>
</ul>
<button data-smark='{"action":"addItem","context":"clips"}'>Add clip</button>
</div>
#myForm ul { display: grid; gap: .75rem; padding: 0; list-style: none; }
#myForm li { display: grid; grid-template-columns: 240px minmax(0, 1fr); grid-template-areas: "video title" "video notes" "video remove"; gap: .5rem .75rem; align-items: start; padding: .75rem; border: 1px solid #ccc; border-radius: .5rem; }
#myForm video { grid-area: video; width: 240px; max-width: 100%; height: auto; }
#myForm input { grid-area: title; box-sizing: border-box; width: 100%; padding: .4rem; }
#myForm textarea { grid-area: notes; box-sizing: border-box; width: 100%; min-height: 5rem; padding: .4rem; resize: vertical; }
#myForm li > button { grid-area: remove; justify-self: start; padding: .2rem .45rem; }
#myForm li > button[aria-label="Remove clip"] { grid-area: remove; }
@media (max-width: 520px) {
#myForm li { grid-template-columns: 1fr; grid-template-areas: "video" "title" "notes" "remove"; }
#myForm video { width: 100%; }
}
const myForm = new SmarkForm(document.getElementById("myForm"), {
"value": {"clips": [{"video": "/assets/video_cat_mixkit.mp4", "title": "Black cat", "notes": "Replace this clip or edit its metadata."}]}
});
👉 Each row is one form value: the clip travels with its title and notes. The video has no native controls, so Download video lets you retrieve the embedded value. Use Add clip to create a row and Remove clip to remove the current row.
Empty State And Playback
An empty field shows an authored native poster, the placeholder option, or the generated play-button poster. A value is displayed through an embedded data: URL. The field never changes authored dimensions, classes, styles, or playback attributes.
Use native controls when the field should be a player. Without controls, smark_video_click: "pick" (the default when controls are absent) makes the video surface an upload affordance. Use smark_video_click: "play" for a programmatic player; Space toggles playback and Shift+Space still opens the picker. The field never enables autoplay.
<video data-smark='{"name":"clip","smark_video_click":"play"}'
width="640" height="360"></video>
SmarkForm does not generate a random thumbnail or open a modal player by default. Capturing a frame requires loading and seeking the media, may fail for browser-incompatible codecs, and adds work before the form is usable. Authors can use native controls or attach their own player/modal to the field’s value and change lifecycle; the stored value remains the original video bytes.
Keyboard And Caption Navigation
The video itself is one SmarkForm field. Enter moves to the next field and Shift+Enter moves to the previous field. When the video has focus:
- Space opens the picker in pick mode, or toggles playback in play mode.
- Shift+Space opens the picker even in play mode.
- Delete or Backspace clears the video when it has a value.
The editable filename caption and trigger buttons are normal browser controls, so Tab and Shift+Tab reach them independently. Smooth Enter/Shift+Enter navigation therefore treats the video as one field, while classic Tab navigation can enter the caption, edit its filename, and then reach upload, download, add, or remove buttons. Delete and Backspace edit the caption while the caption has focus; they clear the video when the video field itself has focus.
Validation And Options
Interactive acquisition probes the bytes with an off-DOM video element and rejects malformed media silently. Set smark_video_validate: false to disable that probe. video_max_size rejects oversized files before reading them and dispatches a cancellable smark:videoNotice (VIDEO_TOO_LARGE); unless a listener calls preventDefault(), SmarkForm also shows an in-page toast. A malformed-media probe failure remains silent.
Common options:
acceptdefaults tovideo/*and filters picker, drop, and paste input.placeholderaccepts a poster URL/data URL orfalse.smark_video_open,smark_video_drop, andsmark_video_pastedisable the corresponding interaction when set tofalse.smark_video_clear_on_deletecontrolsDelete/Backspaceclearing.smark_video_auto_pickopens the picker after rendering whentrue(subject to the browser’s user-gesture policy).formatandencodingare inherited fromfile.
The type does not transcode, resize, or re-encode video. Imported values are trusted and embedded; validation applies to interactive acquisition.
Singletons And Lists
A wrapper containing exactly one inner <video> becomes a singleton. Mark an editable filename with the rename trigger action; SmarkForm sets contenteditable and synchronizes the stored filename automatically:
<figure data-smark='{"type":"video","name":"clip"}'>
<video data-smark width="320" height="180" controls></video>
<figcaption data-smark-action="rename"></figcaption>
</figure>
Lists use of: "video" and inherit file-list acquisition and drop behavior:
<ul data-smark='{"type":"list","name":"clips","of":"video"}'>
<li data-smark='{"type":"video"}'>
<video data-smark width="240" height="135"></video>
</li>
</ul>
OS drops append video items in a video-capable list. Captions remain item-local.
When a list item is a video field, addItem can open the picker immediately as part of the user’s add action. For a form-backed list item containing several fields, use smark_video_auto_pick: true on the particular video field when an automatic picker is desired. This is opt-in and browser-dependent: a picker opened after an asynchronous render may be blocked when no user gesture is active, so an explicit pick trigger remains the reliable fallback.
Browser Limits
The metadata probe confirms that the browser recognizes the media container; it cannot guarantee every codec will play. H.264/AAC MP4 is the safest shared format. Video values are embedded as base64, so use video_max_size to keep memory and data-URL costs appropriate for the application.