Saltar al contenido principal
Version: 2.x

Haptic Feedback

Flarum core provides a haptic() utility for triggering tactile feedback on supported mobile devices. When users perform significant actions — liking a post, submitting a reply, deleting a discussion — a brief vibration reinforces the interaction, making the app feel more responsive and native.

Haptics are supported on:

  • Android — via the Web Vibration API (navigator.vibrate)
  • iOS — via a hidden <input type="checkbox" switch> element that triggers the Taptic Engine (powered by the web-haptics package)

On desktop browsers and unsupported devices, haptic() is a silent no-op — it is always safe to call unconditionally.

User gesture requirement

Both Android and iOS require haptic() to be called within a synchronous user gesture context. Call it before any await or .then() — once execution goes async, the browser's gesture token expires and the haptic will be silently ignored on both platforms.

Basic Usage

Import and call haptic() from flarum/common/utils/haptic:

import haptic from 'flarum/common/utils/haptic';

// Trigger the default preset (light)
haptic();

// Trigger a named preset
haptic('success');

Named Presets

Flarum ships with a set of named presets designed to match the feel of common UI interactions:

PresetFeelSuggested use
'light'Gentle tapToggles, checkbox changes, selections
'medium'Moderate tapGeneral confirmations, secondary actions
'heavy'Strong tapDestructive or irreversible actions
'success'Double tapPositive outcomes (post submitted, liked)
'warning'Double pulseCautionary actions (flagging content)
'error'Triple pulseValidation failures, errors
'nudge'Long + shortAttention, reminders
haptic('light');    // gentle tap — toggles, selections
haptic('medium'); // moderate tap — confirmations
haptic('heavy'); // strong tap — destructive actions
haptic('success'); // double tap — positive actions (e.g. likes)
haptic('warning'); // double pulse — caution
haptic('error'); // triple pulse — validation errors
haptic('nudge'); // long + short — attention, reminders

Custom Patterns

You can also pass a raw vibration duration in milliseconds, or an alternating vibrate/pause sequence:

haptic(50);             // single vibration, 50ms
haptic([100, 50, 100]); // vibrate 100ms, pause 50ms, vibrate 100ms
info

Custom patterns work on both platforms. On Android they use the Web Vibration API directly. On iOS, web-haptics simulates the pattern by clicking the hidden checkbox repeatedly via requestAnimationFrame, producing distinct Taptic Engine taps at the correct intervals. Intensity values are ignored on iOS.

Detecting Support

Use isHapticSupported to conditionally show haptic-related UI in your extension:

import { isHapticSupported } from 'flarum/common/utils/haptic';

if (isHapticSupported) {
// e.g. show a mobile-only tip or UI affordance
}

isHapticSupported is a boolean evaluated once at page load. It is true on Android (Web Vibration API) and iOS (Taptic Engine via checkbox trick), and false on desktop browsers.

User preference

Flarum core includes a built-in haptic feedback toggle in the user's Settings → Device panel. Logged-in users can disable haptics at any time; guests always receive haptic feedback.

haptic() checks this preference automatically — extensions can call it unconditionally without any extra gating.

Where Core Uses Haptics

Flarum core and its bundled extensions apply haptics at the following interaction points:

ActionPresetLocation
Reply posted successfullysuccessReplyComposer
New discussion postedsuccessDiscussionComposer
Post hidden (after confirm)heavyPostControls
Post permanently deleted (after confirm)heavyPostControls
Discussion hiddenheavyDiscussionControls
Discussion restoredsuccessDiscussionControls
Discussion permanently deleted (after confirm)heavyDiscussionControls
Post likedsuccessflarum/likesaddLikeAction
Flag submittedwarningflarum/flagsFlagPostModal
Post approvedsuccessflarum/approvalPostControls
Notification preference toggledlightNotificationGrid

Using Haptics in Extensions

Simple action buttons

The most common use case is triggering haptics when a user completes an action from a button or control:

import haptic from 'flarum/common/utils/haptic';
import Button from 'flarum/common/components/Button';

// In a component view:
<Button
onclick={() => {
haptic('success');
this.performAction();
}}
>
{app.translator.trans('my-extension.forum.action_button')}
</Button>

Before an API save

For actions that involve a server round-trip, trigger the haptic before the async call, while still in the synchronous user gesture context. Calling it inside .then() will be silently ignored on both Android and iOS:

import haptic from 'flarum/common/utils/haptic';

bookmarkAction() {
haptic('success'); // must be synchronous — before any await/.then()
return this.save({ isBookmarked: true }).then(() => m.redraw());
}

Extending core controls with extend()

Use Flarum's extend() helper to add haptics to existing core action methods without modifying them directly. This is the recommended pattern for extensions:

import { extend } from 'flarum/common/extend';
import haptic from 'flarum/common/utils/haptic';
import PostControls from 'flarum/forum/utils/PostControls';
import DiscussionControls from 'flarum/forum/utils/DiscussionControls';

app.initializers.add('my-extension', () => {
// Fire haptic when a post is restored
extend(PostControls, 'restoreAction', function () {
haptic('success');
});

// Fire haptic when a discussion is renamed
extend(DiscussionControls, 'renameAction', function () {
haptic('light');
});
});
tip

extend() runs your callback after the original method. For haptics, this is usually fine — the vibration fires at the moment the action is confirmed. If you need it to fire before the original (e.g. to cancel it conditionally), use override() instead.

Extending component methods

To add haptics to a component method, use extend() on the prototype:

import { extend } from 'flarum/common/extend';
import haptic from 'flarum/common/utils/haptic';
import CommentPost from 'flarum/forum/components/CommentPost';

app.initializers.add('my-extension', () => {
// Example: haptic when user expands a collapsed post
extend(CommentPost.prototype, 'toggleCollapse', function () {
haptic('light');
});
});

Reactions extension example (fof/reactions)

Here is how a reactions extension might integrate haptics — triggering different presets depending on the chosen reaction:

import haptic from 'flarum/common/utils/haptic';

// Map reaction types to haptic presets
const reactionHaptics = {
like: 'success',
love: 'success',
haha: 'light',
angry: 'heavy',
sad: 'medium',
};

function onReactionSelected(reactionType, post) {
const preset = reactionHaptics[reactionType] ?? 'light';
haptic(preset);

post.save({ reaction: reactionType });
}

TypeScript

The HapticInput type is re-exported from flarum/common/utils/haptic (sourced from web-haptics):

import haptic, { isHapticSupported } from 'flarum/common/utils/haptic';
import type { HapticInput } from 'flarum/common/utils/haptic';

// HapticInput accepts a preset name, a duration in ms, or a vibrate/pause array
const preset: HapticInput = 'success';
const duration: HapticInput = 50;
const pattern: HapticInput = [100, 50, 100];

// haptic(pattern?: HapticInput): void
haptic(preset);
haptic(pattern);
haptic(); // defaults to 'light'

API Reference

ExportTypeDescription
haptic (default)(pattern?: HapticInput) => voidTrigger haptic feedback
isHapticSupportedbooleanWhether the device supports haptics
HapticInputtypeRe-exported from web-haptics — preset name, ms duration, or pattern array