NakedTooltip

Hover/focus triggered tooltip overlay with positioning and lifecycle callbacks

Headless tooltip component. Handles hover/focus triggers, positioning, timers, and dismissal. Use builder pattern for custom styling.

When to use this

  • Help text: Explain UI elements or provide additional context
  • Icon explanations: Clarify what icon buttons do
  • Form field hints: Show validation rules or input examples
  • Feature descriptions: Brief explanations of complex features

Basic implementation

import 'package:flutter/material.dart';
import 'package:naked_ui/naked_ui.dart';

class TooltipExample extends StatefulWidget {
  const TooltipExample({super.key});

  @override
  State<TooltipExample> createState() => _TooltipExampleState();
}

class _TooltipExampleState extends State<TooltipExample>
    with SingleTickerProviderStateMixin {
  late final AnimationController _controller = AnimationController(
    duration: const Duration(milliseconds: 200),
    vsync: this,
  );

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return NakedTooltip(
      positioning: const OverlayPositionConfig(
        targetAnchor: Alignment.topCenter,
        followerAnchor: Alignment.bottomCenter,
      ),
      waitDuration: const Duration(milliseconds: 400),
      showDuration: const Duration(seconds: 2),
      onOpen: () => _controller.forward(),
      onClose: () => _controller.reverse(),
      child: Container(
        padding: const EdgeInsets.all(10),
        decoration: BoxDecoration(
          color: Colors.grey.shade900,
          borderRadius: BorderRadius.circular(8),
        ),
        child: const Icon(Icons.copy, color: Colors.white, size: 18),
      ),
      overlayBuilder: (context, info) {
        return FadeTransition(
          opacity: _controller,
          child: Material(
            elevation: 4,
            borderRadius: BorderRadius.circular(8),
            child: Padding(
              padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
              child: const Text('Copy to clipboard'),
            ),
          ),
        );
      },
      semanticsLabel: 'Copy button tooltip',
    );
  }
}

Constructor

const NakedTooltip({
  Key? key,
  required this.child,
  required this.overlayBuilder,
  this.showDuration = const Duration(seconds: 2),
  this.waitDuration = const Duration(seconds: 1),
  this.positioning = const OverlayPositionConfig(
    targetAnchor: Alignment.topCenter,
    followerAnchor: Alignment.bottomCenter,
  ),
  this.onOpen,
  this.onClose,
  this.onOpenRequested,
  this.onCloseRequested,
  this.semanticsLabel,
})

Key Parameters

  • child → trigger widget that displays the tooltip on hover/focus
  • overlayBuilder(BuildContext, RawMenuAnchorOverlayInfo) that returns the tooltip body each time it shows
  • showDuration → how long the tooltip stays visible before hiding automatically
  • waitDuration → delay before showing once hover/focus occurs
  • positioningOverlayPositionConfig controlling anchor & offset
  • onOpen / onClose → lifecycle hooks useful for animations
  • onOpenRequested / onCloseRequested → intercept show/hide requests (e.g. async delays)
  • semanticsLabel → text announced by assistive technologies on hover/focus

Behaviour Notes

  • Tooltips open on pointer hover and keyboard focus
  • When waitDuration elapses, the overlay opens via RawMenuAnchor; timers reset on exit
  • The overlay hides after showDuration; supply onClose for exit animations
  • The trigger remains focusable; use semanticsLabel for descriptive text when the trigger has no visible label

Positioning Tips

Use OverlayPositionConfig.offset to nudge the tooltip position:

const OverlayPositionConfig(
  targetAnchor: Alignment.topCenter,
  followerAnchor: Alignment.bottomCenter,
  offset: Offset(0, -8),
);

The overlay builder receives RawMenuAnchorOverlayInfo (info in the sample) with anchorRect, overlaySize, and position should you need pointer coordinates for arrow placement.