question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. ItĀ collects links to all the places you might be looking at while hunting down a tough bug.

And, if youā€™re still stuck at the end, weā€™re happy to hop on a call to see how we can help out.

Better ergonomics around showing arrows in Tooltips

See original GitHub issue

šŸš€ Feature request

Current Behavior

At the time of writing, itā€™s not entirely intuitive to support arrows on tooltips (which is a fairly common use case). The docs suggest I need to drop down to low-level useTooltip APIs and place the tooltip myself.

The docs tell me to do my own position function to center the tooltips to ā€œkeep the positioning logic simplerā€.

Desired Behavior

But I still want dynamic positioning with collision detection.

I think either

  • the docs should show how to position tooltips with arrows with the current API, or
  • the library itself should support arrows out of the box and handle the positioning (perhaps opt-in through a prop, I donā€™t know).

The latter is preferred, I think.

Suggested Solution

Right now, I inlined the tooltips.tsx source into our app and hacked the Reach Tooltip code to render an arrow div along with the main tooltip.

export type TooltipContentProps = {
    ariaLabel?: string;
    position?: Position;
    /** Provide an optional positioning function for the arrow. */
+   positionArrow?: Position;
    label: React.ReactNode;
    isVisible?: boolean;
    triggerRect: DOMRect | null;
};
function TooltipContent(
   // ...
   position = positionDefault,
+  positionArrow = positionArrowDefault,

We need to let the arrow know about collisions, so we can make it point downwards:

export type Collisions = {
  left: boolean;
  right: boolean;
  bottom: boolean;
  top: boolean;
};

const getCollisions = (
  triggerRect: DOMRect,
  tooltipRect: DOMRect
): Collisions => ({
  top: triggerRect.top - tooltipRect.height < 0,
  right: window.innerWidth < triggerRect.left + tooltipRect.width,
  bottom:
      window.innerHeight < triggerRect.bottom + tooltipRect.height + OFFSET,
  left: triggerRect.left - tooltipRect.width < 0,
});

const positionArrow: Position = (triggerRect, tooltipRect, collisions) => {
    if (!triggerRect || !tooltipRect || !collisions) {
        return {};
    }

    const directionUp = collisions.bottom && !collisions.top;

    return {
        left: triggerRect.left - 10 + triggerRect.width / 2,
        top: directionUp
            ? triggerRect.bottom + window.pageYOffset - tooltipRect.height - 10
            : triggerRect.bottom + window.pageYOffset,
        ...(directionUp
            ? {
                  transform: 'rotate(180deg)',
              }
            : undefined),
    };
};

Then add the arrow below the tooltip in TooltipContent:

const collisions =
   !!triggerRect && !!tooltipRect
      ? getCollisions(triggerRect, tooltipRect)
      : undefined;

return (
  <Fragment>
    <Comp
      data-reach-tooltip
      role={useAriaLabel ? undefined : 'tooltip'}
      id={useAriaLabel ? undefined : id}
      children={label}
      style={{
        ...style,
        ...(!tooltipRect
          ? {
              visibility: 'hidden',
            }
          : position(triggerRect, tooltipRect, collisions)),
      }}
      ref={ref}
      {...rest}
    />
    <div
      data-reach-tooltip-arrow
      style={{
        position: 'absolute',
        width: 0,
        height: 0,
        ...(!tooltipRect
          ? {
              visibility: 'hidden',
            }
          : positionArrow(triggerRect, tooltipRect, collisions)),
      }}
    />
    {useAriaLabel && (
      <VisuallyHidden role="tooltip" id={id}>
        {ariaLabel}
      </VisuallyHidden>
    )}
  </Fragment>
);

See complete code in the gist linked below.


Iā€™m sure this can be made much cleaner, but I just needed to get the job done. I think the authors have a better starting point šŸ˜„

I imagine the API could be:

<Tooltip label="Foobar" arrow>
  <button>Hey</button>
</Tooltip>

This suggests these additions to TooltipContentProps:

export type TooltipContentProps = {
    ariaLabel?: string;
    position?: Position;
    label: React.ReactNode;
    isVisible?: boolean;
    triggerRect: DOMRect | null;

    // New šŸ„ 
    arrow?: boolean; // Default to false
    positionArrow?: Position; // Default to a nice positioning function, but let users customise
};

Then the library code takes complete care about:

  • positioning the tooltips with collision detection.
  • positioning the arrows accordingly.
  • providing custom positioning functions for both tooltip and arrows as a part of the external API.

Who does this impact? Who is this for?

Most people wanting tooltips in web apps.

Describe alternatives youā€™ve considered

Most other tooltip libs provide out-of-the-box solutions for rendering tooltips. See Reakit: https://reakit.io/docs/tooltip/.

Additional context

Complete gist of my modified code: https://gist.github.com/brookback/67bb0de295c8af96f50ce6bec8cf4cd8.


Thanks for a great, hackable library! ā¤ļø

Issue Analytics

  • State:closed
  • Created 4 years ago
  • Reactions:4
  • Comments:7 (1 by maintainers)

github_iconTop GitHub Comments

2reactions
chaancecommented, Mar 11, 2020

Awesome write-up, thank you! As it stands I think this is still better handled by docs and examples than new props or abstract components. Reach UI is intended to be fairly low-level, and we want you to build your own abstractions on top of ours. Iā€™ll take a closer look at your fork code to see if I canā€™t figure out better ways we might handle it either in the docs or some better example code.

1reaction
brookbackcommented, Mar 12, 2020

Thanks for replying šŸ™ !

Whilst I totally understand you donā€™t want to bog down the lib with arrow functionality, I think, as you say, something can be done to clarify how to get the best of both worlds: the built-in positioning and arrows that behave in a good way.

The problem I ran into when positioning my arrows (without resorting to centering the whole tooltip horizontally and disallowing collision detection) was that I needed references to

  • tooltipRect
  • triggerRect

in order to calculate collisions and heights. The current API doesnā€™t expose tooltipRect (if Iā€™m not mistaken).

Read more comments on GitHub >

github_iconTop Results From Across the Web

Tooltip Guidelines
4. Use tooltip arrows when multiple elements are nearby. Arrows are beneficial to clearly identify to which element the tooltip is associated.
Read more >
Styling the arrow on bootstrap tooltips [duplicate]
Show activity on this post. The arrow is a border. You need to change for each arrow the color depending on the 'data-placement'...
Read more >
HTX50 Ergonomic Professional Hammer Tacker
The HTX50 delivers the proven performance of the HT50 with upgraded ergonomics. The high impact molded handle with rubber anti-slip hand grip deliversĀ ......
Read more >
Tooltip arrow over buttons - Microsoft Power BI Community
Solved: Does anyone know how to get rid of the tooltip arrow that displays when hovering over a button? I have tooltips left...
Read more >
Tableau tool tip tip week - Dynamic symbols in tool tips
Tool tips are an extremely useful way to provide your users with ... whether a figure is positive (up arrow) or negative (down...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found