Hotkeys
π Table of Contents
Overview
SmarkForm supports keyboard shortcuts (hotkeys) for trigger components. Hotkeys let users activate any trigger β addItem, removeItem, export, clear, or any custom action β without reaching for the mouse.
Hotkeys are:
- Opt-in β only triggers that declare a
hotkeyoption participate. - Context-sensitive β the same key can mean different things depending on which component currently has keyboard focus.
- Discoverable β pressing and holding
Ctrlreveals all active hotkeys as visual hints on their trigger buttons, so users can discover them on the fly rather than needing to memorise a list. A little CSS setup is needed β see Hotkey Reveal.
Defining a Hotkey
Add the hotkey property to a triggerβs data-smark object:
<div id="myForm">
<button data-smark='{"action":"addItem","context":"phones","hotkey":"+"}' title="Add phone">β Add Phone</button>
<ul data-smark='{"type":"list","name":"phones"}'>
<li>
<input type="tel" data-smark placeholder="Phone number">
<button data-smark='{"action":"removeItem","hotkey":"-"}' title="Remove">β</button>
</li>
</ul>
</div>
const myForm = new SmarkForm(document.getElementById("myForm"));
Hold Ctrl to reveal the + and - hotkey hints on the buttons. Press Ctrl++ to add a phone, Ctrl+- to remove one. The hotkey value must match KeyboardEvent.key (e.g. "+", "-", "Enter", single letters).
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βΌοΈ
π οΈ 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. Disabled for this example.βΆοΈ Runβ (only visible in edit mode) re-renders the Preview from the current editor contents and switches to the Preview tab.
The value of hotkey must match the browserβs KeyboardEvent.key string for the desired key (e.g. "+", "-", "s", "Enter", β¦). Single printable characters are the most portable and least likely to conflict with browser or OS shortcuts.
Hotkey Reveal (Ctrl Discovery)
When the user presses and holds Ctrl, SmarkForm:
- Finds all trigger components that have a
hotkeyoption and whose context is an ancestor (or sibling ancestor) of the currently focused element. - Sets the
data-hotkeyattribute on each matching trigger button to the configured key character.
Your CSS can use this attribute to show a visual hint:
<div id="myForm">
<button data-smark='{"action":"addItem","context":"phones","hotkey":"+"}' title="Add phone">β Add Phone</button>
<ul data-smark='{"type":"list","name":"phones"}'>
<li>
<input type="tel" data-smark placeholder="Phone number">
<button data-smark='{"action":"removeItem","hotkey":"-"}' title="Remove">β</button>
</li>
</ul>
</div>
/* Anchor the badge to its button */
button { position: relative; }
/* Show hotkey badge on the button */
[data-hotkey]::after {
content: "Ctrl+" attr(data-hotkey);
position: absolute; top: -1.6em; left: 0;
font-size: 0.7em;
background: #333; color: #fff;
padding: 1px 4px; border-radius: 3px;
white-space: nowrap;
}
const myForm = new SmarkForm(document.getElementById("myForm"));
The data-hotkey attribute is added to hotkey-equipped trigger buttons while Ctrl is held. Style its ::after pseudo-element to create a floating tooltip. The button must have position: relative for the absolute-positioned badge.
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βΌοΈ
π οΈ 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. Disabled for this example.βΆοΈ Runβ (only visible in edit mode) re-renders the Preview from the current editor contents and switches to the Preview tab.
When Ctrl is released, the data-hotkey attributes are removed and the hints disappear.
Pressing a key while Ctrl is held activates the matching trigger (if one was revealed).
Holding
Ctrl+Altreveals a second level of hotkeys. This is useful when the same key is used for multiple actions at different nesting levels β pressingCtrlshows the innermost one,Ctrl+Altshows an outer one.
Context Sensitivity
Hotkeys are scoped to the focus context. The same key binding can be re-used in different parts of the form without conflict:
<div id="myForm">
<ul data-smark='{"type":"list","name":"users"}'>
<li>
<input data-smark type="text" name="name" placeholder="User name">
<ul data-smark='{"type":"list","name":"phones"}'>
<li>
<input type="tel" data-smark name="phone" placeholder="Phone">
<!-- Ctrl+- removes a phone when focus is inside the phones list -->
<button data-smark='{"action":"removeItem","hotkey":"-"}'>β Phone</button>
</li>
</ul>
<button data-smark='{"action":"addItem","context":"phones","hotkey":"+"}'>β Add phone</button>
<!-- Ctrl+- removes a user when focus is at the user level (outside phones) -->
<button data-smark='{"action":"removeItem","hotkey":"-"}'>β Remove user</button>
</li>
</ul>
<button data-smark='{"action":"addItem","context":"users","hotkey":"+"}'>β Add user</button>
</div>
/* Hold Ctrl to reveal hotkey hints */
button{position:relative}
[data-hotkey]::after{
content:"Ctrl+" attr(data-hotkey);
position:absolute; top:-1.6em; left:0;
font-size:0.7em;
background:#333; color:#fff;
padding:1px 4px; border-radius:3px;
white-space:nowrap;
}
const myForm = new SmarkForm(document.getElementById("myForm"), {
"value": {"users":[{"name":"Alice","phones":[{"phone":"555-1234"}]},{"name":"Bob","phones":[{"phone":"555-5678"}]}]}
});
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βΌοΈ
π οΈ 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. Disabled for this example.βΆοΈ Runβ (only visible in edit mode) re-renders the Preview from the current editor contents and switches to the Preview tab.
When the focus is inside the phones list, pressing Ctrl+- removes a phone. When focus is at the user level (but not inside phones), Ctrl+- removes the user instead.
Conflict Resolution
When two triggers share the same hotkey at the same context level, SmarkForm uses the following priority rules to decide which one fires:
- Distance from focus β triggers whose context is a closer ancestor of the focused element are preferred.
- Containment β when distance is equal, a trigger whose
targetcontains the focused element takes precedence over a sibling trigger.
Only the highest-priority trigger for each key is revealed and activated. Lower-priority duplicates are silently ignored.
A trigger that is disabled at the moment the hotkey is pressed will not fire, even if it was revealed. The
data-hotkeyattribute is also omitted from disabled buttons so they do not appear in the visual hints.
Hiding Trigger Buttons (While Keeping Hotkeys)
As we already learned, a SmarkForm hotkey is defined over a trigger component, so making an action reachable from the keyboard means placing a trigger that calls that action somewhere in the form β the very same trigger touch users tap.
This aligns with SmarkFormβs philosophy of offering the same functionality regardless of the device or input method: on a touch device you will hardly use the keyboard (let alone a hotkey), but you can always tap the button.
There are, nevertheless, cases where a hotkey is convenient but flooding the form with triggers for a non-essential action would clutter it more than needed.
π In the examples above, every phone row carries its own β and β buttons so you can remove or insert a phone at that exact position. On a small screen the buttons at the top of the form are enough β they always act at the end of the list β yet they cannot target an individual row the way the buttons attached to the row can.
π‘ In this scenario we can use CSS to hide the per-row triggers while keeping their hotkeys working.
Keep in mind that if, like above, you show the hotkey hint through a
::beforeor::afterpseudo-element, you must not hide the button with a property that removes it from the box tree, such asdisplay: none;β that would also prevent the pseudo-element from appearing.Better use
visibility: hidden;oropacity: 0;to hide the button, together withwidth: 0px;and/orheight: 0px;as needed to keep it from taking space in the layout.
This is just a CSS trick rather than a SmarkForm feature, but it helps build smoother and cleaner forms: when you hold Ctrl, the +/- hints appear next to the (now hidden) per-row β/β buttons, while the buttons that stay visible let touch users add or remove phones from the end of the list.
Accessibility Considerations
- Avoid browser-reserved combinations β
Ctrl+C,Ctrl+V,Ctrl+Z,Ctrl+A,F1βF12,Alt+F4, etc. are used by browsers and operating systems. Prefer punctuation characters (+,-,n,d, β¦) that are not typically claimed. - Provide visible alternatives β hotkeys are a convenience feature. Every action that has a hotkey should also be reachable with the mouse so that users on touch devices or those who do not discover the hotkeys can still operate the form.
- Label your buttons β use
titleattributes or visible text on trigger buttons so their purpose is clear even without hotkey hints.
Further Examples
When inner and outer list items share the same hotkey (e.g. Ctrl+- for removing both a phone AND a contact entry), the innermost matching context wins by default. To reach the outer-level action, hold Ctrl+Alt instead β this suppresses inner hotkeys and reveals the next level out.
<div id="myForm">
<div data-smark='{"type":"list","name":"phonelist"}'>
<fieldset>
<legend>
<input name="name" data-smark placeholder="Name">
<button data-smark='{"action":"removeItem","hotkey":"-"}' title="Remove">β</button>
</legend>
<button data-smark='{"action":"addItem","context":"phones","hotkey":"+"}' title="Add phone">β Add phone</button>
<ul data-smark='{"type":"list","name":"phones"}'>
<li>
<input type="tel" data-smark placeholder="Phone">
<button data-smark='{"action":"removeItem","hotkey":"-"}' title="Remove">β</button>
</li>
</ul>
</fieldset>
</div>
<button data-smark='{"action":"addItem","context":"phonelist","hotkey":"+"}' title="Add entry">β Add entry</button>
</div>
const myForm = new SmarkForm(document.getElementById("myForm"));
Ctrl+- removes a phone when focus is inside the phones list, but removes the whole entry when focus is outside it. Press Ctrl+Alt to reveal the 2nd-level + hotkey on the outer βAdd entryβ button.
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βΌοΈ
π οΈ 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. Disabled for this example.βΆοΈ Runβ (only visible in edit mode) re-renders the Preview from the current editor contents and switches to the Preview tab.
The Showcase contains exhaustive real-world examples that demonstrate hotkeys in context, including nested lists, multi-level hotkey levels, and mixed trigger layouts.