Exercise feedback
Run arbitrary code when an exercise check succeeds or fails
Every exercise accepts onSuccess and onFailure callbacks. Use them to play a sound, show a notification, or run any other function when the student checks their answers.
The shared API is documented under Check callbacks. This page shows customisation examples you can drop into a book page (atoms.tsx or hotspot content).
Play a sound
Put audio files in the book's public/ folder (or any URL the book can load) and play them from the callbacks:
import { YesNo } from '@bside-tech/tbx-ui/components';
function playSound(src: string) {
void new Audio(src).play();
}
<YesNo
entries={[
{ text: 'The Earth is a planet.', answer: true },
{ text: 'Water boils at 0°C at sea level.', answer: false },
]}
onSuccess={() => playSound('/sounds/success.mp3')}
onFailure={() => playSound('/sounds/try-again.mp3')}
/>If you do not want to ship audio files, a short Web Audio beep works in the browser:
function beep(frequency: number) {
const ctx = new AudioContext();
const oscillator = ctx.createOscillator();
const gain = ctx.createGain();
oscillator.frequency.value = frequency;
oscillator.connect(gain);
gain.connect(ctx.destination);
gain.gain.setValueAtTime(0.08, ctx.currentTime);
gain.gain.exponentialRampToValueAtTime(0.001, ctx.currentTime + 0.15);
oscillator.start();
oscillator.stop(ctx.currentTime + 0.15);
}
<YesNo
entries={entries}
onSuccess={() => beep(880)}
onFailure={() => beep(220)}
/>Browsers may block audio until the student has interacted with the page. Clicking Check counts as that interaction.
With checkOn: "auto", onFailure can fire on every answer change. Prefer onSuccess for sounds in auto mode, or debounce the handler yourself.
Show a notification
Books already mount HeroUI's <ToastProvider /> in src/layout.tsx — the same provider used when the theme changes. Call toast() from the callbacks:
- The Earth is a planet.
- Water boils at 0°C at sea level.
import { toast } from '@heroui/react';
import { YesNo } from '@bside-tech/tbx-ui/components';
<YesNo
entries={[
{ text: 'The Earth is a planet.', answer: true },
{ text: 'Water boils at 0°C at sea level.', answer: false },
]}
onSuccess={() =>
toast('Well done', {
description: 'Every answer is correct.',
variant: 'success',
})
}
onFailure={() =>
toast('Try again', {
description: 'Not quite. Reset and check once more.',
variant: 'danger',
})
}
/>That matches the theme-switcher pattern (toast("Theme changed to", { description, variant: "success" })). Use variant: "success" or "danger" (or toast.success / toast.danger) depending on the result.
Arbitrary functions
The callbacks are plain functions. Call anything you need:
<PickOne
entries={entries}
onSuccess={() => {
window.dispatchEvent(new CustomEvent('tbx:exercise-success'));
console.log('capital question correct');
}}
onFailure={() => {
analytics.track('exercise_failed', { id: 'capital-france' });
}}
/>Page-wide defaults
Wrap a hotspot or page in <ExerciseFeedback> so every nested exercise shares the same handlers. Per-exercise props still override the defaults.
- The Earth is a planet.
- Water boils at 0°C at sea level.
- JavaScript and Java are the same language.
import { toast } from '@heroui/react';
import { ExerciseFeedback, YesNo } from '@bside-tech/tbx-ui/components';
<ExerciseFeedback
onSuccess={() =>
toast('Correct', {
description: 'This exercise is complete.',
variant: 'success',
})
}
onFailure={() =>
toast('Try again', {
description: 'Reset and check once more.',
variant: 'danger',
})
}
>
<YesNo entries={firstEntries} />
<YesNo entries={secondEntries} />
</ExerciseFeedback>