Help and Tooltips

Help people understand a setting

Use an info button next to a field or group title. It opens after 300 ms on hover or immediately on keyboard focus. Click or tap to keep the explanation open; click again, click outside, or press Escape to close it. The native touch long-press gesture also opens help, including on underlined text.

Allow rotation

Use when artwork reaches the cut edge.

HelpDemo.svelte

<script lang="ts">
	import { Help } from '@leon8ix/jhana-ui';
</script>

<div class="stack">
	<div class="field-head">
		<span>Allow rotation</span>
		<Help
			label="Help: Allow rotation"
			text="Pages may turn by 90° to fit more copies on the sheet. The original file is unchanged."
		/>
	</div>
	<p>
		Use <Help text="The extra printed area beyond the cut edge prevents white edges after cutting.">bleed</Help> when
		artwork reaches the cut edge.
	</p>
</div>

Hover delays

Choose instant, normal (300 ms), or long (800 ms). Normal is the default for Help. Use long for secondary explanations that should appear only when someone pauses to investigate. Keyboard focus always opens help immediately. Clicking or tapping these dedicated Help triggers also opens it immediately. Native touch long press works with every tooltip trigger, regardless of its hover delay.

Once a tooltip opens on hover, move to another tooltip trigger within 150 ms to show its explanation immediately. This continues across triggers, including long-delay actions, until you spend 150 ms away from them. Try waiting on Long below, then moving between the three labels. The tooltip itself closes immediately when you leave its trigger. Click a Help trigger to pin its explanation if you want to select text inside it.

TooltipDelayDemo.svelte

<script lang="ts">
	import { Help } from '@leon8ix/jhana-ui';
</script>

<div class="row">
	<Help delay="instant" text="Shows immediately on hover.">Instant</Help>
	<Help text="Shows after 300 ms on hover.">Normal</Help>
	<Help delay="long" text="Shows after 800 ms on hover.">Long</Help>
</div>

Instant help for radio choices

Radio tooltips appear instantly on hover by default, so you can compare explanations while moving between options. Attach to the native input; its label supplies the visible target. On touchscreens, a tap selects the option and keeps its explanation open. Selecting another option replaces it; tapping elsewhere dismisses it. An explicit delay option overrides the instant default. With long delay, ordinary clicks and taps only select the option and dismiss help by default. Use a long hover, keyboard focus, or native touch long press to read that optional explanation.

Hover-only help closes immediately when you leave the radio choice, even after a long hover. Moving onto the tooltip itself does not keep it open.

Recipient

RadioButtonsDemo.svelte

<script lang="ts">
	import { tooltip } from '@leon8ix/jhana-ui/actions';

	const uid = $props.id();
	const options = [
		...[1, 4, 5, 6].map(number => ({
			value: `sb${number}`,
			label: `No. ${number}`,
			help: `Sends the file to the Portal folder on self-service PC ${number}. Choose the printer number assigned to you.`,
		})),
		{
			value: 'tmp',
			label: 'Employee',
			help: 'For excellent print quality or a specific paper, let an employee print the file for you.',
		},
	];
	let recipient = $state('tmp');
</script>

<fieldset class="field-group">
	<legend>Recipient</legend>
	<div class="btn-group">
		{#each options as option}
			{@const id = `${uid}-${option.value}`}
			<input
				type="radio"
				name={uid}
				{id}
				value={option.value}
				bind:group={recipient}
				{@attach tooltip(option.help)}
			/>
			<label class="btn dark" class:primary={recipient === option.value} for={id}>{option.label}</label>
		{/each}
	</div>
</fieldset>

Optional help for choices

Set openOnSelect to false when radio or checkbox clicks and taps should only change the value and dismiss help. This works independently of the hover delay. Infinipose's SVG, PDF and HPGL preview modes use long delay, which already defaults to this behavior. Keyboard focus and native touch long press still open their explanations. For normal-delay choices such as paper formats, pass delay: 'normal' and openOnSelect: false explicitly. Put each format's explanation on its own choice, rather than explaining all choices in a tooltip on the legend.

Preview

OptionalRadioHelpDemo.svelte

<script lang="ts">
	import { Radio } from '@leon8ix/jhana-ui';
	import { tooltip } from '@leon8ix/jhana-ui/actions';

	const uid = $props.id();
	let preview = $state('svg');
</script>

<fieldset class="field-group">
	<legend>Preview</legend>
	<div class="input-group boxed">
		<Radio
			name={uid}
			label="SVG"
			value="svg"
			bind:group={preview}
			{@attach tooltip('Fast preview. The source file is displayed as a raster image.', { delay: 'long' })}
		/>
		<Radio
			name={uid}
			label="PDF"
			value="pdf"
			bind:group={preview}
			{@attach tooltip(
				'High-quality vector preview with precise zoom for detailed inspection. Loads more slowly.',
				{ delay: 'long' },
			)}
		/>
		<Radio
			name={uid}
			label="HPGL"
			value="hpgl"
			bind:group={preview}
			{@attach tooltip(
				'Shows the cutting and creasing commands processed by ColorCut. HPGL is its internal instruction format.',
				{ delay: 'long' },
			)}
		/>
	</div>
</fieldset>

Secondary help for icon actions

ActionButton automatically uses its label as a tooltip with a long hover delay (800 ms). Specify title only when you want a fuller explanation; it replaces the tooltip text without creating a native browser tooltip. On touchscreens, long-press the action to read its tooltip. The browser's native context-menu gesture controls when it opens; releasing the gesture does not run the action. A normal tap just runs the action.

Try the reset icon below: a click or tap sets Copies to one. No tooltip attachment is needed. Use a separate Help button when people should be able to discover the explanation directly.

ActionTooltipDemo.svelte

<script lang="ts">
	import { ActionButton, NumberInput, iconReset } from '@leon8ix/jhana-ui';

	let copies = $state<number | null>(3);
</script>

<div class="fields" style:grid-template-columns="minmax(0, max-content) auto">
	<NumberInput label="Copies" min={1} bind:value={copies} />
	<div class="field-accessory">
		<ActionButton
			icon={iconReset}
			label="Reset copies"
			title="Restore the number of copies to one."
			onclick={() => (copies = 1)}
		/>
	</div>
</div>

Text and Image

Place the image first in the snippet, followed by a paragraph. The tooltip gives the image equal top and side insets, with corner curves centered on the box's own curves. Include dimensions to reserve space and meaningful alt text to explain the diagram.

Bleed

ImageHelpDemo.svelte

<script lang="ts">
	import { Help } from '@leon8ix/jhana-ui';
	import bleedImage from './bleed.svg';
</script>

{#snippet bleedHelp()}
	<img
		src={bleedImage}
		alt="Bleed surrounds the finished artwork outside the dashed cut edge."
		width="400"
		height="240"
	/>
	<p>Extend the artwork beyond the cut edge so small cutting shifts do not leave white borders.</p>
{/snippet}

<div class="field-head">
	<span>Bleed</span>
	<Help label="Help: Bleed illustration" content={{ snippet: bleedHelp }} />
</div>

Image Only

A snippet can contain just an image, with the same inset on all four sides and matching corner centers. Its alt text must convey the explanation without the picture.

ImageOnlyHelpDemo.svelte

<script lang="ts">
	import { Help } from '@leon8ix/jhana-ui';
	import bleedImage from './bleed.svg';
</script>

{#snippet diagram()}
	<img
		src={bleedImage}
		alt="Bleed is the outer green area. The dashed rectangle marks the final cut edge."
		width="400"
		height="240"
	/>
{/snippet}

<Help content={{ snippet: diagram }}>Show the cut edge</Help>

Custom Content

Use the same snippet or component object convention as context menus. A component receives no props; wrap it in a snippet to pass props or use reactive values. Field and NumberInput accept the same content in their help prop, and tooltip(content) accepts it on an existing control.

Place a custom visual in a leading tooltip-media element, followed by tooltip-body for its title and text. These are top-level elements in the component, with no enclosing layout wrapper. They use the same inset, image-to-text gap and concentric corners as the image examples. The component only styles the diagram itself.

°

ComponentHelpDemo.svelte

<script lang="ts">
	import { Help, NumberInput } from '@leon8ix/jhana-ui';
	import RotationHelp from './RotationHelp.svelte';

	let angle = $state<number | null>(90);
</script>

{#snippet currentRotation()}
	<RotationHelp angle={angle ?? 90} />
{/snippet}

<div class="fields">
	<NumberInput
		label="Rotation step"
		bind:value={angle}
		min={15}
		max={90}
		step={15}
		unit="°"
		help={{ snippet: currentRotation }}
	/>
	<div class="field-accessory">
		<Help content={{ component: RotationHelp }}>About rotation steps</Help>
	</div>
</div>

The component owns its explanatory markup and styles:

RotationHelp.svelte

<script lang="ts">
	let { angle = 90 }: { angle?: number } = $props();
</script>

<div class="tooltip-media preview" aria-hidden="true">
	<div class="sheet"><div class="page" style:rotate="{angle}deg"></div></div>
	<span>{angle}°</span>
</div>
<div class="tooltip-body stack" style="gap: var(--space-1)">
	<strong>Rotation steps</strong>
	<p>Try each orientation in {angle}° steps when fitting pages onto the sheet.</p>
</div>

<style>
	.preview {
		display: flex;
		align-items: center;
		justify-content: center;
		gap: var(--space-3);
		padding: var(--space-3);
		background: color-mix(in srgb, currentColor 8%, transparent);
	}
	.sheet {
		display: grid;
		place-items: center;
		width: 7em;
		height: 7em;
		border: 1px dashed currentColor;
		border-radius: 0.4em;
	}
	.page {
		width: 3em;
		height: 4.5em;
		border-radius: 0.2em;
		background: currentColor;
		opacity: 0.65;
	}
</style>

Choose the right place for the explanation

Essential instructions belong in a visible field hint. Tooltips contain supplementary text, images or custom explanations. Use dotted-underlined Help text to explain a term in prose, and the tooltip attachment to describe an existing focusable control.

For the selectable button-style pattern used above, see Radio Buttons. Keep essential information visible; a long press is supplementary help, not the only way to understand an action.

Keep help buttons beside labels, never inside them. Tooltips do not contain links or buttons; longer interactive content belongs in a popup.

Tooltips use native popovers and fit the available viewport. The first Escape dismisses the tooltip while keeping its containing dialog open. Focus stays on the trigger.

The full API and interaction contract ship in @leon8ix/jhana-ui/docs/help.md. Start agent work at @leon8ix/jhana-ui/docs/README.md.