Revise Haptics explainer to include declarative method#1282
Revise Haptics explainer to include declarative method#1282
Conversation
|
|
||
| Both the imperative and declarative paths share the same effect vocabulary. | ||
|
|
||
| ### Effect Vocabulary |
There was a problem hiding this comment.
To help explain where this set came from and help those familiar with a particular platform's haptics already, consider adding a proposed mapping to the various platform presets:
e.g.
https://developer.apple.com/documentation/appkit/nshapticfeedbackmanager/feedbackpattern#Constants
etc.
There was a problem hiding this comment.
Added an example table.
|
|
||
| Future work may address transition-end haptics (`haptic-transition`), multi-step haptic sequences (`@haptic` at-rule). | ||
|
|
||
| ## Alternatives Considered |
There was a problem hiding this comment.
Did you explore haptics being triggered as part of animations or transitions?
(Caveat that I haven't given it a lot of thought yet)
Initial spitball proposal (very rough):
- New
animation-haptic-effectandanimation-haptic-intensityattributes. Assignable with the values you'd expect from your explainer, and assignable by all the rules of normal CSS attributes. These always describe what the behavior should be, just likeanimation-durationcan be set anytime and computed anytime. - Anytime an animation is performed, the UA checks the effective
animation-haptic-effecton the element being animated. If there is one, it starts the new haptic event with the start of the animation. If the animation is cancelled, so is the haptic event. - No restrictions on user interaction/etc. (other than the initial one for page engagement)
Open questions:
- How should competing haptic events be handled? Probably should only have a single winner at any point in time. Last started one wins? Most intense one wins for ties? Longest duration one wins for ties?
- If the element being animated is not visible, should the haptic event be suppressed?
There was a problem hiding this comment.
This is a really interesting thought and I originally had this as a future extension to the proposal. I generated an alternative explainer with transition/animation as the primary declarative approach just so that we have something to compare. Been tweaking on a comparison table below which feels reasonable. I think they are complementary, and the question is what we lead with in the beginning (think we should scope to one for now).
Right now I'm leaning more on the pseudo-class, given it's more ergonomic and more broadly applicable but with drawback on standards novelty. Open to difference in opinion @mhochk , and I'm curious if @kbabbitt have thoughts on this too.
Regardless of which we lead with, I plan to flesh out the alternative and thought process a bit more in the alternatives considered section so that the reasoning is clear and we can easily course-correct if we get more feedback.
Shared Foundation
Both approaches propose the same imperative JS API (navigator.playHaptics), effect vocabulary (hint/edge/tick/align/none), and scroll-snap-haptic property. They diverge only in the declarative CSS surface and converge on the same long-term vision — each lists the other's constructs as a future extension.
Two Approaches
Pseudo-class primary (haptic-feedback property): Fires a haptic when an element transitions into a pseudo-class state (:hover, :active, :checked, :focus, etc.) due to direct user interaction. Two new CSS constructs. Core principle: "user changed state → fire haptic."
Transition primary (haptic-transition, @haptic/haptic-animation): Fires haptics at transition completion, and enables multi-step choreographed sequences synced to CSS animations. Three new CSS constructs. Core principle: "something moved visually → fire haptic."
Where They Overlap
For styled interactive elements — buttons with hover/active transitions, custom toggles with sliding thumbs, form validation with shake animations, scroll-snap carousels — either approach works declaratively. This is the majority of real-world haptics use cases.
Where They Diverge
Pseudo-class exclusively covers direct state-change interactions without requiring any authored motion: hover, active, focus, checked, and validation states on elements that have no CSS transition. This is broader than just unstyled native controls — many functional UIs have interactive elements without transitions.
Transition exclusively enables multi-step choreographed haptics synchronized to visual motion: a modal entrance ramping hint → tick → align, a progress bar with escalating pulses, page transitions with multi-beat feedback. Neither pseudo-class nor the imperative API can replicate this.
Key Tradeoffs
| Dimension | Pseudo-class first | Transition first |
|---|---|---|
| No-JS coverage breadth | Broader — covers every pseudo-class interaction regardless of whether the element has visual motion | Narrower — requires an existing transition or animation |
| Unique new capability | One-shot haptics on state entry (coverable by one-line JS) | Multi-step choreography synced to motion (not replicable by other means) |
| CSS novelty / precedent risk | Higher — introduces an unprecedented pattern: a CSS property that fires a discrete non-visual side-effect on state entry. Sets a precedent other proposals may follow, which the CSS WG may resist | Lower — every construct maps to established patterns (transitionend, @keyframes, animation) |
| Spec/implementation surface | Smaller (2 constructs, simpler semantics) | Larger (3 constructs, timeline synchronization, at-rule) |
| Value of adding the other later | Adding transition later provides genuinely new capability (choreography) | Adding pseudo-class later provides syntactic convenience for interactions already coverable by imperative fallback |
| Developer mental model | Simpler — "state change = haptic" | Requires understanding transition lifecycle, sync-with() linking |
Sequencing Arguments
Case for pseudo-class first: It covers a broader set of everyday interactions declaratively. A platform primitive should first handle the common cases authors encounter constantly. It ships a smaller, simpler v1 and leaves rich choreography as a strong follow-on that adds genuinely new expressive power — a clean "baseline then enrichment" progression.
Case for transition first: It front-loads the richer, harder-to-replicate capability on safer CSS ground. It avoids establishing an unprecedented CSS property precedent, giving the proposal a smoother standards path. If transition ships first and pseudo-class never materializes, the imperative API covers the gap adequately. If pseudo-class ships first and transition never materializes, the richest declarative use cases are lost — the downside risk is asymmetric.
Both are defensible. The choice depends on whether the team prioritizes broadest no-JS baseline coverage and smallest v1 (→ pseudo-class) or smoothest standards path and unique new capability (→ transition).
There was a problem hiding this comment.
I don't see a PR for leaving comment on the alternate explainer, so I'll just leave my core comment here:
It looks like sync-with is still experimental, so I would advise not depending on it yet.
What about something closer to my above proposal of making this part of the animation, rather than something independent that syncs to it? It would certainly be a lot more restrictive, but would also mean a lot less edges to handle. (And if making that shift for animations, it would make sense to do the same for transitions as well).
There was a problem hiding this comment.
I made this (low-ish effort, I haven't gone through/edited the whole explainer yet) which I think is what you described?
I'm liking it more and more. We can start with that as the easier-to-standardize phase 1, and move the pseudoclass part as a future extension to allow for easier authoring/wider coverage. I will refactor the explainer.
There was a problem hiding this comment.
Made a major update, would appreciate your thoughts
|
|
||
| Future work may address transition-end haptics (`haptic-transition`), multi-step haptic sequences (`@haptic` at-rule). | ||
|
|
||
| ## Alternatives Considered |
There was a problem hiding this comment.
Alternative for the imperative form - what about extending the existing navigator.vibrate API to also accept keywords instead of raw patterns? I don't know how common that is in the JavaScript land, but in CSS land this would be very normal (e.g. color can take explicit precise colors, or keywords supplied by the browser).
There was a problem hiding this comment.
I expanded the alt considered section to be clearer -
Extending navigator.vibrate — The existing vibrate() API accepts raw duration/pattern arrays (e.g. navigator.vibrate([100, 50, 200])) with no way to express semantic intent like "tick" or "align." Adding named effects would require method overloading or a new options-bag signature, complicating an already-shipped interface. Feature detection becomes awkward — typeof navigator.vibrate tells you the method exists but not whether it supports named effects. The pattern-based model also encourages developers to hand-tune durations per device, which is the opposite of the platform-adaptive approach this proposal targets. Finally, vibrate() lacks broad engine support (absent in Safari/WebKit) and carries existing abuse stigma that could slow adoption of legitimate haptic use cases.
No description provided.