Scrollable Element Shadow Hook

· 329 words · 2 minute read

Add a right/left shadow cue for a scrollable element that exceeds its container width.

For example, it is common practice to use traditional elements for displaying datasets. However, managing tables with numerous columns that exceeds the container width can be challenging. Although the table may be scrollable, it may not be immediately clear to the user.

To address this, it is helpful to provide users with a visual cue indicating horizontal scrollability, such as adding a vertical shadow border.

Introducing a new hook that adds a right shadow when the table is scrollable to the left and a left shadow when it’s scrollable to the right. With this hook, you can enhance the user experience by intuitively indicating table scrollability.

import { useCallback, useEffect, useMemo, useState } from "react";

export function useHorizontalScrollShadow({ ref }) {
  const [hasLeftShadow, setHasLeftShadow] = useState(false);
  const [hasRightShadow, setHasRightShadow] = useState(false);

  const updateShadowVisibility = useCallback(() => {
    if (!ref?.current) {
      return;
    }

    setHasLeftShadow(ref.current.scrollLeft > 0);
    setHasRightShadow(
      ref.current.scrollWidth >
        ref.current.clientWidth + ref.current.scrollLeft + 1
    );
  }, [setHasLeftShadow, setHasRightShadow, ref]);

  useEffect(() => {
    if (!ref?.current) {
      return;
    }

    updateShadowVisibility();
  }, [ref]);

  const boxShadow = useMemo(() => {
    const leftShadow = hasLeftShadow ? "2px 0 2px 0 rgba(0, 0, 0, 0.1)" : "";
    const rightShadow = hasRightShadow ? "-2px 0 2px 0 rgba(0, 0, 0, 0.1)" : "";

    if (leftShadow && rightShadow) {
      return `${leftShadow}, ${rightShadow}`;
    }

    return leftShadow || rightShadow;
  }, [hasLeftShadow, hasRightShadow]);

  return { updateShadowVisibility, boxShadow };
}

In the following example, we import the hook and call it with the table reference. The hook will return the boxShadow and a callback for the onScroll event. With the onScroll event we can keep shadows updated.

import { useRef } from "react";
import { useHorizontalScrollShadow } from "./useHorizontalScrollShadow";

const ScrollableTable = () => {
  const tableRef = useRef(null);
  const { updateShadowVisibility, boxShadow } = useHorizontalScrollShadow({
    ref: tableRef,
  });

  return (
    <div>
      <table
        ref={tableRef}
        onScroll={updateShadowVisibility}
        style={{ boxShadow }}
      >
        {/*  */}
      </table>
    </div>
  );
};

export default ScrollableTable;