<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: hritickjaiswal</title>
    <description>The latest articles on Forem by hritickjaiswal (@hritickjaiswal).</description>
    <link>https://forem.com/hritickjaiswal</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F548615%2F4c3dc8fb-5c76-4879-8fa5-496d1b4fb6d6.png</url>
      <title>Forem: hritickjaiswal</title>
      <link>https://forem.com/hritickjaiswal</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/hritickjaiswal"/>
    <language>en</language>
    <item>
      <title>Building an Accessible Toast Notification System in React</title>
      <dc:creator>hritickjaiswal</dc:creator>
      <pubDate>Sun, 29 Mar 2026 06:15:36 +0000</pubDate>
      <link>https://forem.com/hritickjaiswal/building-an-accessible-toast-notification-system-in-react-a65</link>
      <guid>https://forem.com/hritickjaiswal/building-an-accessible-toast-notification-system-in-react-a65</guid>
      <description>&lt;p&gt;Toast notifications are often implemented as simple UI feedback components, but making them accessible requires additional considerations. In this implementation, the focus was not just on functionality but also on ensuring the component works well for &lt;strong&gt;screen reader users, keyboard users, and assistive technologies&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Accessibility Goals&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The toast system was designed with the following accessibility goals:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Notifications should be &lt;strong&gt;announced to screen readers&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Users should have &lt;strong&gt;enough time to read the message&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Keyboard users should get the &lt;strong&gt;same behavior as mouse users&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Users should be able to &lt;strong&gt;manually dismiss notifications&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Screen Reader Announcements&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;To make sure assistive technologies announce notifications automatically, the toast container is implemented as a live region.&lt;/p&gt;



&lt;p&gt;This tells screen readers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;role="status" → This region contains status updates.&lt;/li&gt;
&lt;li&gt;aria-live="polite" → Announce updates when the user is idle.&lt;/li&gt;
&lt;li&gt;aria-atomic="true" → Read the entire notification message.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When a new toast is added, screen readers automatically announce the message without interrupting the user’s current task.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pause on Interaction&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Auto-dismissing notifications can be problematic if users don't have enough time to read them. To address this, the toast timer pauses when the user interacts with the notification.&lt;/p&gt;

&lt;p&gt;onMouseEnter → pause timer&lt;br&gt;
onMouseLeave → resume timer&lt;br&gt;
onFocus → pause timer&lt;br&gt;
onBlur → resume timer&lt;/p&gt;

&lt;p&gt;Supporting both &lt;strong&gt;hover and focus&lt;/strong&gt; ensures the behavior works for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Mouse users&lt;/li&gt;
&lt;li&gt;Keyboard users navigating with the tab key&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Accessible Dismiss Button&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Each toast includes a dismiss button so users can remove notifications manually.&lt;/p&gt;

&lt;p&gt;X&lt;/p&gt;

&lt;p&gt;The aria-label ensures screen readers clearly communicate the purpose of the button, even though the visible content is just an "X".&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Non-Disruptive Notifications&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Another important accessibility consideration is not stealing focus when a toast appears.&lt;/p&gt;

&lt;p&gt;This implementation ensures that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Notifications appear visually&lt;/li&gt;
&lt;li&gt;Screen readers announce them&lt;/li&gt;
&lt;li&gt;The user's current focus remains unchanged&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This prevents interruptions while users are typing or navigating through the interface.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Supporting Multiple Notifications&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The system also supports stacked toasts, allowing multiple notifications to appear without overlapping content. A maximum limit prevents excessive notifications from overwhelming users.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why Accessibility Matters Here&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Toast notifications may seem small, but poorly implemented ones can create problems for assistive technology users. By incorporating:&lt;/p&gt;

&lt;p&gt;ARIA live regions&lt;br&gt;
keyboard interaction support&lt;br&gt;
pause-on-interaction behavior&lt;br&gt;
accessible controls&lt;/p&gt;

&lt;p&gt;the component ensures notifications remain usable and understandable for all users.&lt;/p&gt;

&lt;p&gt;Accessibility often comes down to small decisions in implementation, and even simple components like toast notifications can benefit from thoughtful design.&lt;/p&gt;

</description>
      <category>a11y</category>
      <category>react</category>
      <category>frontend</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Improving Accessibility - Tooltip</title>
      <dc:creator>hritickjaiswal</dc:creator>
      <pubDate>Sun, 22 Feb 2026 06:50:39 +0000</pubDate>
      <link>https://forem.com/hritickjaiswal/improving-accessibility-tooltip-3ogc</link>
      <guid>https://forem.com/hritickjaiswal/improving-accessibility-tooltip-3ogc</guid>
      <description>&lt;p&gt;Tooltips look simple.&lt;/p&gt;

&lt;p&gt;They are not.&lt;/p&gt;

&lt;p&gt;If you’ve ever tried building one properly, you’ll realize quickly that it’s not about styling — it’s about behavior.&lt;/p&gt;

&lt;p&gt;In this article, I’ll walk through a simple accessible Tooltip implementation in React and focus only on three important aspects:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;⏱ Time interval handling (prevent flicker)&lt;/li&gt;
&lt;li&gt;⌨ Escape key handling (keyboard accessibility)&lt;/li&gt;
&lt;li&gt;📍 CSS positioning (clean layout without layout hacks)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  The Goal
&lt;/h3&gt;

&lt;p&gt;We want a tooltip that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Appears on hover and focus&lt;/li&gt;
&lt;li&gt;Disappears on mouse leave and blur&lt;/li&gt;
&lt;li&gt;Closes when pressing Escape&lt;/li&gt;
&lt;li&gt;Doesn’t flicker on quick mouse movement&lt;/li&gt;
&lt;li&gt;Is positioned cleanly using CSS&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Implementation
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import {
  useEffect,
  useRef,
  useState,
  cloneElement,
  isValidElement,
  type ReactElement,
  useId,
} from "react";

import styles from "./style.module.css";

interface TooltipProps {
  children: ReactElement;
  title: string;
}

const DELAY_INTERVAL = 200;

function Tooltip({ children, title }: TooltipProps) {
  const [showTitle, setShowTitle] = useState(false);
  const openTimer = useRef(-1);
  const closeTimer = useRef(-1);
  const tooltipId = useId();

  function show() {
    clearTimeout(openTimer.current);

    openTimer.current = setTimeout(() =&amp;gt; setShowTitle(true), DELAY_INTERVAL);
  }

  function hide() {
    clearTimeout(closeTimer.current);
    clearTimeout(openTimer.current);

    closeTimer.current = setTimeout(() =&amp;gt; setShowTitle(false), DELAY_INTERVAL);
  }

  useEffect(() =&amp;gt; {
    function keydownHandler(e: KeyboardEvent) {
      const { key } = e;

      if (key === "Escape") {
        setShowTitle(false);
      }
    }

    if (showTitle) {
      document.addEventListener("keydown", keydownHandler);
    }

    return () =&amp;gt; {
      if (showTitle) {
        document.removeEventListener("keydown", keydownHandler);
      }
    };
  }, [showTitle]);

  if (!isValidElement(children)) {
    throw new Error("Tooltip expects a single React element child");
  }

  const trigger = cloneElement(children, {
    onMouseEnter: (e) =&amp;gt; {
      children.props.onMouseEnter?.(e);
      show();
    },
    onMouseLeave: (e) =&amp;gt; {
      children.props.onMouseLeave?.(e);
      hide();
    },
    onFocus: (e) =&amp;gt; {
      children.props.onFocus?.(e);
      show();
    },
    onBlur: (e) =&amp;gt; {
      children.props.onBlur?.(e);
      hide();
    },
    "aria-describedby": showTitle ? tooltipId : undefined,
  });

  return (
    &amp;lt;div className={styles.container}&amp;gt;
      {trigger}

      {showTitle ? (
        &amp;lt;div className={styles.titleContainer}&amp;gt;
          &amp;lt;div role="tooltip" id={tooltipId} className={styles.titleWrapper}&amp;gt;
            {title}
          &amp;lt;/div&amp;gt;
        &amp;lt;/div&amp;gt;
      ) : null}
    &amp;lt;/div&amp;gt;
  );
}

export default Tooltip;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;CSS&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;.container {
  position: relative;
}

.titleContainer {
  position: absolute;
  left: 50%;
  top: calc(100%);
  transform: translateX(-50%);
  padding-top: 8px;
}

.titleWrapper {
  padding: 0.25rem;
  background-color: #afafaf;
  color: #fff;
  border-radius: 0.25rem;
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Why this works well:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;No layout shifts&lt;/li&gt;
&lt;li&gt;No DOM measurements&lt;/li&gt;
&lt;li&gt;No scroll listeners&lt;/li&gt;
&lt;li&gt;No portal complexity&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>a11y</category>
      <category>react</category>
      <category>css</category>
      <category>frontend</category>
    </item>
    <item>
      <title>Improving Accessibility - Tabs Component</title>
      <dc:creator>hritickjaiswal</dc:creator>
      <pubDate>Wed, 04 Feb 2026 02:42:16 +0000</pubDate>
      <link>https://forem.com/hritickjaiswal/improving-accessibility-tabs-component-3ceh</link>
      <guid>https://forem.com/hritickjaiswal/improving-accessibility-tabs-component-3ceh</guid>
      <description>&lt;p&gt;In this iteration I will be picking *&lt;em&gt;Tabs Component *&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Tabs?
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Tabs look simple, but they force you to think about:&lt;/li&gt;
&lt;li&gt;Keyboard navigation (Arrow keys vs Tab key)&lt;/li&gt;
&lt;li&gt;Focus management&lt;/li&gt;
&lt;li&gt;ARIA roles and relationships&lt;/li&gt;
&lt;li&gt;Disabled state handling&lt;/li&gt;
&lt;li&gt;Component composition and APIs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In interviews and real apps, Tabs are a very common a11y failure point.&lt;/p&gt;

&lt;h2&gt;
  
  
  Key Concepts Used in This Implementation
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Compound Component Pattern
&lt;/h3&gt;

&lt;p&gt;Instead of one giant  component, we expose:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;Tabs&amp;gt;
  &amp;lt;TabList&amp;gt;
    &amp;lt;Tab /&amp;gt;
  &amp;lt;/TabList&amp;gt;
  &amp;lt;PanelList&amp;gt;
    &amp;lt;TabPanel /&amp;gt;
  &amp;lt;/PanelList&amp;gt;
&amp;lt;/Tabs&amp;gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Keeps the API expressive&lt;/li&gt;
&lt;li&gt;Avoids prop drilling&lt;/li&gt;
&lt;li&gt;Mirrors how headless UI libraries are designed&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2. Children.map + cloneElement
&lt;/h3&gt;

&lt;p&gt;We need to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Inject index&lt;/li&gt;
&lt;li&gt;Attach refs&lt;/li&gt;
&lt;li&gt;Preserve user-defined JSX&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is where Children.map and cloneElement shine.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. forwardRef
&lt;/h3&gt;

&lt;p&gt;Keyboard navigation requires programmatic focus movement.&lt;br&gt;
To do that, the parent must be able to call .focus() on each .&lt;/p&gt;

&lt;p&gt;forwardRef enables that cleanly.&lt;/p&gt;
&lt;h3&gt;
  
  
  4. Accessibility First
&lt;/h3&gt;

&lt;p&gt;This component follows WAI-ARIA Tabs Authoring Practices, including:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;role="tablist", tab, tabpanel&lt;/li&gt;
&lt;li&gt;aria-selected, aria-controls, aria-labelledby&lt;/li&gt;
&lt;li&gt;Roving tabIndex&lt;/li&gt;
&lt;li&gt;Disabled tab skipping&lt;/li&gt;
&lt;li&gt;Automatic activation model&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Accessibility Behavior (What This Component Supports)
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;ArrowLeft / ArrowRight navigate between tabs&lt;/li&gt;
&lt;li&gt;Only one tab is tabbable at a time&lt;/li&gt;
&lt;li&gt;Disabled tabs are skipped during keyboard navigation&lt;/li&gt;
&lt;li&gt;Panels are hidden from screen readers when inactive&lt;/li&gt;
&lt;li&gt;Focus never jumps unexpectedly&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Full Source Code
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import type React from "react";
import {
  createContext,
  useState,
  type ReactNode,
  Children,
  cloneElement,
  useContext,
  useRef,
  forwardRef,
  isValidElement,
  useEffect,
} from "react";
import styles from "./style.module.css";

/**
 * TabsContext holds shared state for the compound components
 */
const TabsContext = createContext&amp;lt;{
  activeIndex: number;
  setActiveIndex: React.Dispatch&amp;lt;React.SetStateAction&amp;lt;number&amp;gt;&amp;gt;;
  onChange?: (index: number) =&amp;gt; void;
  disableMap: Array&amp;lt;boolean&amp;gt;;
  setDisableMap: React.Dispatch&amp;lt;React.SetStateAction&amp;lt;boolean[]&amp;gt;&amp;gt;;
} | null&amp;gt;(null);

/**
 * Safe context hook
 */
// eslint-disable-next-line react-refresh/only-export-components
export const useTabsContext = () =&amp;gt; {
  const context = useContext(TabsContext);
  if (context === null) {
    throw new Error(
      "useTabsContext must be used within a TabsContext.Provider"
    );
  }
  return context;
};

interface TabsProps {
  children: ReactNode;
  onChange?: (index: number) =&amp;gt; void;
}

interface TabListProps {
  children: ReactNode;
}

interface PanelListProps {
  children: ReactNode;
}

type TabProps = React.ComponentPropsWithRef&amp;lt;"button"&amp;gt; &amp;amp; {
  index: number;
  children: ReactNode;
};

interface TabPanelProps {
  index: number;
  children: ReactNode;
}

export function TabList({ children }: TabListProps) {
  const { activeIndex, setActiveIndex, onChange, disableMap } =
    useTabsContext();
  const tabsRef = useRef&amp;lt;Array&amp;lt;HTMLButtonElement&amp;gt;&amp;gt;([]);
  const childrenLength = Children.count(children);

  function handleKeyDown(e: React.KeyboardEvent&amp;lt;HTMLDivElement&amp;gt;) {
    const { key } = e;

    /**
     * Find next enabled tab (wraps around)
     */
    const getNextIndex = () =&amp;gt; {
      for (let i = activeIndex + 1; i &amp;lt; childrenLength; i++) {
        if (!disableMap[i]) return i;
      }

      for (let i = 0; i &amp;lt; activeIndex; i++) {
        if (!disableMap[i]) return i;
      }

      return activeIndex;
    };

    /**
     * Find previous enabled tab (wraps around)
     */
    const getPrevIndex = () =&amp;gt; {
      for (let i = activeIndex - 1; i &amp;gt;= 0; i--) {
        if (!disableMap[i]) return i;
      }

      for (let i = childrenLength - 1; i &amp;gt;= activeIndex; i--) {
        if (!disableMap[i]) return i;
      }

      return activeIndex;
    };

    if (key === "ArrowRight") {
      e.preventDefault();
      const nextIndex = getNextIndex();

      tabsRef.current[nextIndex]?.focus();
      setActiveIndex(nextIndex);

      if (typeof onChange === "function") {
        onChange(nextIndex);
      }
    } else if (key === "ArrowLeft") {
      e.preventDefault();
      const prevIndex = getPrevIndex();

      tabsRef.current[prevIndex]?.focus();
      setActiveIndex(prevIndex);
      if (typeof onChange === "function") {
        onChange(prevIndex);
      }
    }
  }

  return (
    &amp;lt;div
      onKeyDown={handleKeyDown}
      className={styles.btnContainer}
      role="tablist"
      aria-orientation="horizontal"
    &amp;gt;
      {Children.map(children, (child, index) =&amp;gt;
        cloneElement(child, {
          index,
          ref: (el: HTMLButtonElement | null) =&amp;gt; {
            tabsRef.current[index] = el;
          },
        })
      )}
    &amp;lt;/div&amp;gt;
  );
}

export function PanelList({ children }: PanelListProps) {
  return (
    &amp;lt;div className={styles.panelConatiner}&amp;gt;
      {Children.map(children, (child, index) =&amp;gt; {
        if (!isValidElement(child)) return null;

        return cloneElement(child, {
          index,
        });
      })}
    &amp;lt;/div&amp;gt;
  );
}

export const Tab = forwardRef&amp;lt;HTMLButtonElement, TabProps&amp;gt;((props, ref) =&amp;gt; {
  const { index, children, disabled, ...restProps } = props;
  const { activeIndex, setActiveIndex, onChange, setDisableMap } =
    useTabsContext();

  function clickHandler() {
    setActiveIndex(index);

    if (typeof onChange === "function") {
      onChange(index);
    }
  }

  /**
   * Register disabled state in parent
   */
  useEffect(() =&amp;gt; {
    setDisableMap((prev) =&amp;gt; {
      const temp = [...prev];
      temp[index] = !!disabled;

      return temp;
    });
  }, [disabled, index]);

  return (
    &amp;lt;button
      onClick={clickHandler}
      className={`${styles.tabBtn} ${
        activeIndex === index ? styles.activeBtn : ""
      }`}
      role="tab"
      aria-controls={`panel-${index}`}
      ref={ref}
      tabIndex={activeIndex === index ? 0 : -1}
      aria-selected={activeIndex === index ? "true" : "false"}
      id={`tab-${index}`}
      disabled={disabled}
      aria-disabled={disabled ? "true" : "false"}
      {...restProps}
    &amp;gt;
      {children}
    &amp;lt;/button&amp;gt;
  );
});

export function TabPanel({ children, index }: TabPanelProps) {
  const { activeIndex } = useTabsContext();
  return (
    &amp;lt;div
      className={index !== activeIndex ? styles.hidden : ""}
      role="tabpanel"
      aria-labelledby={`tab-${index}`}
      hidden={index !== activeIndex}
    &amp;gt;
      {children}
    &amp;lt;/div&amp;gt;
  );
}

/**
 * Root Tabs component
 */
function Tabs({ children, onChange }: TabsProps) {
  const [activeIndex, setActiveIndex] = useState(0);
  const [disableMap, setDisableMap] = useState&amp;lt;Array&amp;lt;boolean&amp;gt;&amp;gt;(() =&amp;gt;
    Array(Children.count(children)).fill(false)
  );

  return (
    &amp;lt;TabsContext.Provider
      value={{
        activeIndex,
        setActiveIndex,
        onChange,
        disableMap,
        setDisableMap,
      }}
    &amp;gt;
      {children}
    &amp;lt;/TabsContext.Provider&amp;gt;
  );
}

export default Tabs;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Styles&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;.tabBtn:focus {
  outline: 2px solid orangered;
  outline-offset: 4px;
}

.activeBtn {
  background-color: green;
  color: #fff;
  font-weight: 700;
}

.hidden {
  display: none;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I learned and revised more concepts than I expected before I started working on this and was really fun.&lt;/p&gt;

&lt;p&gt;Any suggestions and improvements are always welcome&lt;/p&gt;

</description>
      <category>react</category>
      <category>javascript</category>
      <category>a11y</category>
      <category>typescript</category>
    </item>
    <item>
      <title>Improving Accessibility - Typehead</title>
      <dc:creator>hritickjaiswal</dc:creator>
      <pubDate>Sun, 25 Jan 2026 16:33:40 +0000</pubDate>
      <link>https://forem.com/hritickjaiswal/improving-accessibility-typehead-3fnd</link>
      <guid>https://forem.com/hritickjaiswal/improving-accessibility-typehead-3fnd</guid>
      <description>&lt;p&gt;Accessibility isn't just a "nice-to-have" feature—it’s the difference between a usable tool and a digital brick for many users. This Typeahead component balances performance (debouncing), reliability (aborting race conditions), and strict ARIA compliance.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. The Power of aria-activedescendant&lt;/strong&gt;&lt;br&gt;
Instead of manually moving focus between list items (which is a nightmare for screen readers), this component uses the aria-activedescendant pattern.&lt;/p&gt;

&lt;p&gt;The Logic: The focus remains on the , while the aria-activedescendant attribute points to the ID of the currently "highlighted" list item.&lt;/p&gt;

&lt;p&gt;Why it matters: It allows the user to keep typing while the screen reader announces the current selection in the list.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Concurrency &amp;amp; Race Conditions&lt;/strong&gt;&lt;br&gt;
When fetching suggestions over a network, speed varies. If a user types "Apple" and then quickly "Banana," the "Apple" request might finish after "Banana," overwriting your state with stale data.&lt;/p&gt;

&lt;p&gt;The Fix: Using AbortController and a latestRequestRef.&lt;/p&gt;

&lt;p&gt;How it works: Every time the search query changes, we abort the previous request and use a ref to ensure only the most recent fetch updates the state.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Smart Keyboard Ergonomics&lt;/strong&gt;&lt;br&gt;
A component is only as good as its keyboard support. This implementation handles:&lt;/p&gt;

&lt;p&gt;ArrowUp/Down: Navigates the list with index wrapping (going from the last item back to the first).&lt;/p&gt;

&lt;p&gt;Enter: Selects the active item and closes the menu.&lt;/p&gt;

&lt;p&gt;Escape: Immediately dismisses the dropdown to prevent focus traps.&lt;/p&gt;

&lt;p&gt;Automatic Scrolling: The useEffect with scrollIntoView({ block: "nearest" }) ensures that if you keyboard-navigate to an item off-screen, the list scrolls to show it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Performance: The Custom useDebounce&lt;/strong&gt;&lt;br&gt;
We don't want to fire an API call for every single keystroke.&lt;/p&gt;

&lt;p&gt;Implementation: The custom useDebounce hook delays the update of the search term.&lt;/p&gt;

&lt;p&gt;The Result: If the user types "React" quickly, only one network request is fired instead of five.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Semantic Feedback&lt;/strong&gt;&lt;br&gt;
The use of aria-live="polite" and aria-busy ensures that users are notified when the list is loading or if an error occurs. Without these, a screen reader user would have no idea if the component is working or if the search failed.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import {
  useEffect,
  useRef,
  useState,
  type ChangeEvent,
  type FocusEvent,
  type KeyboardEvent,
  type MouseEvent,
} from "react";
import styles from "./style.module.css";

export interface OptionType {
  label: string;
  value: string;
  id: number;
}

interface TypeheadProps {
  label: string;
  options: Array&amp;lt;OptionType&amp;gt;;
  onSelect?: (obj: OptionType) =&amp;gt; void;
  fetchSuggestions?: (
    query: string,
    signal: AbortSignal
  ) =&amp;gt; Promise&amp;lt;OptionType[]&amp;gt;;
}

function useDebounce&amp;lt;T&amp;gt;(value: T, delay = 300): T {
  const [debouncedValue, setDebouncedValue] = useState(() =&amp;gt; value);

  useEffect(() =&amp;gt; {
    const timerid = setTimeout(() =&amp;gt; {
      setDebouncedValue(value);
    }, delay);

    return () =&amp;gt; {
      clearTimeout(timerid);
    };
  }, [value, delay]);

  return debouncedValue;
}

function Typehead({
  label,
  options,
  onSelect,
  fetchSuggestions,
}: TypeheadProps) {
  const [show, setShow] = useState(false);
  const [input, setInput] = useState("");
  const [searchedItems, setSearchedItems] = useState&amp;lt;Array&amp;lt;OptionType&amp;gt;&amp;gt;([]);
  const [activeId, setActiveId] = useState&amp;lt;null | number&amp;gt;(null);
  const [selectedLabel, setSelectedLabel] = useState("");
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState("");
  const debouncedValue = useDebounce(input);

  const wrapperRef = useRef&amp;lt;HTMLDivElement&amp;gt;(null);
  const listRef = useRef&amp;lt;HTMLUListElement&amp;gt;(null);
  const latestRequestRef = useRef(0);

  function onChangeHandler(e: ChangeEvent&amp;lt;HTMLInputElement&amp;gt;) {
    setInput(e.target.value);
    setSelectedLabel("");
  }

  function onFocusHandler(e: FocusEvent&amp;lt;HTMLInputElement, Element&amp;gt;) {
    setShow(true);
  }

  function onBlurHandler(e: FocusEvent&amp;lt;HTMLInputElement, Element&amp;gt;) {
    setShow(false);
  }

  function onMouseDownHandler(
    e: MouseEvent&amp;lt;HTMLLIElement, globalThis.MouseEvent&amp;gt;,
    obj: OptionType
  ) {
    e.preventDefault();
    setInput("");
    setSelectedLabel(obj.label);
    if (typeof onSelect === "function") onSelect(obj);
    setActiveId(obj.id);
    setShow(false);
  }

  function keyDownHandler(e: KeyboardEvent) {
    const { key } = e;

    if (key === "Escape") {
      setShow(false);
    } else if (searchedItems.length &amp;gt; 0) {
      if (key === "ArrowUp" &amp;amp;&amp;amp; activeId === null) {
        e.preventDefault();
        setActiveId(searchedItems[searchedItems.length - 1].id);
      } else if (key === "ArrowUp") {
        e.preventDefault();
        let index = searchedItems.findIndex((obj) =&amp;gt; obj.id === activeId);

        index = index - 1 &amp;lt; 0 ? searchedItems.length - 1 : index - 1;

        setActiveId(searchedItems[index].id);
      } else if (key === "ArrowDown" &amp;amp;&amp;amp; activeId === null) {
        e.preventDefault();
        setActiveId(searchedItems[0].id);
      } else if (key === "ArrowDown") {
        e.preventDefault();
        let index = searchedItems.findIndex((obj) =&amp;gt; obj.id === activeId);

        index = index + 1 &amp;gt;= searchedItems.length ? 0 : index + 1;

        setActiveId(searchedItems[index].id);
      } else if (key === "Enter" &amp;amp;&amp;amp; activeId !== null) {
        const obj = searchedItems.find((e) =&amp;gt; e.id === activeId);

        if (obj) {
          setInput("");
          setSelectedLabel(obj.label);
          if (typeof onSelect === "function") onSelect(obj);
          setActiveId(obj.id);
          setShow(false);
        }
      }
    }
  }

  async function handleFetch(query: string, signal: AbortSignal) {
    if (typeof fetchSuggestions === "function") {
      const requestId = ++latestRequestRef.current;

      try {
        setLoading(true);
        setError("");

        const temp = await fetchSuggestions(query, signal);

        if (requestId === latestRequestRef.current) {
          setSearchedItems(temp);
        }
      } catch (error) {
        if (signal.aborted) return;

        console.error(error);
        setError("Error");
      } finally {
        setLoading(false);
      }
    }
  }

  useEffect(() =&amp;gt; {
    let controller: AbortController;

    if (typeof fetchSuggestions === "function") {
      console.log("debouncedValue", debouncedValue);

      if (debouncedValue.length === 0) {
        setSearchedItems([]);
        return;
      }

      controller = new AbortController();

      handleFetch(debouncedValue, controller.signal);
    } else {
      if (debouncedValue.length === 0) {
        setSearchedItems(options);
      } else {
        const temp = options.filter((option) =&amp;gt;
          option.value.toLowerCase().includes(debouncedValue.toLowerCase())
        );

        setSearchedItems(temp);
        setShow(true);
      }
    }

    return () =&amp;gt; {
      if (typeof fetchSuggestions === "function" &amp;amp;&amp;amp; controller) {
        controller.abort();
      }
    };
  }, [debouncedValue, fetchSuggestions, options]);

  useEffect(() =&amp;gt; {
    if (show) {
      try {
        for (const child of listRef.current?.children) {
          const id = child.getAttribute("data-id");

          if (Number(id) === activeId) {
            child.scrollIntoView({
              behavior: "auto", // "auto" provides the instant jump you want
              block: "nearest", // Prevents unnecessary shifting if already in view
              inline: "nearest", // Applies the same logic horizontally
            });
            return;
          }
        }
      } catch (error) {
        console.error(error);
      }
    }
  }, [activeId, show]);

  return (
    &amp;lt;div ref={wrapperRef} className={styles.wrapper}&amp;gt;
      &amp;lt;label className={styles.label} htmlFor={`${label}-dropdown`}&amp;gt;
        {label}
      &amp;lt;/label&amp;gt;
      &amp;lt;input
        onChange={onChangeHandler}
        value={input || selectedLabel}
        className={styles.input}
        id={`${label}-dropdown`}
        onFocus={onFocusHandler}
        onBlur={onBlurHandler}
        aria-expanded={show}
        role="combobox"
        aria-activedescendant={
          show &amp;amp;&amp;amp; activeId !== null &amp;amp;&amp;amp; activeId &amp;gt;= 0
            ? `option-${activeId}`
            : undefined
        }
        onKeyDown={keyDownHandler}
        aria-controls={show ? "listbox" : undefined}
        aria-autocomplete="list"
      /&amp;gt;

      {show ? (
        &amp;lt;ul
          id="listbox"
          role="listbox"
          ref={listRef}
          tabIndex={-1}
          className={styles.list}
        &amp;gt;
          {error.length &amp;gt; 0 ? (
            &amp;lt;li aria-busy="true" aria-live="polite" className={styles.listItem}&amp;gt;
              Error
            &amp;lt;/li&amp;gt;
          ) : loading ? (
            &amp;lt;li aria-busy="true" aria-live="polite" className={styles.listItem}&amp;gt;
              Loading...
            &amp;lt;/li&amp;gt;
          ) : searchedItems.length &amp;gt; 0 ? (
            &amp;lt;&amp;gt;
              {searchedItems.map((obj) =&amp;gt; (
                &amp;lt;li
                  id={`option-${obj.id}`}
                  role="option"
                  aria-selected={activeId === obj.id}
                  onMouseDown={(e) =&amp;gt; onMouseDownHandler(e, obj)}
                  className={`${styles.listItem} ${
                    activeId === obj.id ? styles.active : ""
                  }`}
                  key={obj.id}
                  data-id={obj.id}
                &amp;gt;
                  {obj.label}
                &amp;lt;/li&amp;gt;
              ))}
            &amp;lt;/&amp;gt;
          ) : (
            &amp;lt;li aria-live="polite" role="option" className={styles.listItem}&amp;gt;
              No filtered items found
            &amp;lt;/li&amp;gt;
          )}
        &amp;lt;/ul&amp;gt;
      ) : null}
    &amp;lt;/div&amp;gt;
  );
}

export default Typehead;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And let's not forget how much CSS is important for accessibility&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;.list {
  position: absolute;
  list-style: none;
  background-color: lightgray;
  display: flex;
  flex-direction: column;
  overflow-y: auto;
  max-height: 240px;
  width: 100%;
  transform: translateY(80px);
}

.listItem {
  padding: 0.5rem 0.25rem;
  cursor: pointer;
  color: #000;
}

.listItem.active {
  background-color: springgreen;
}

.listItem:hover {
  /* background-color: springgreen; */
  background-color: #a5e356;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>a11y</category>
      <category>javascript</category>
      <category>react</category>
      <category>architecture</category>
    </item>
    <item>
      <title>Improving Accessibility – Dropdown</title>
      <dc:creator>hritickjaiswal</dc:creator>
      <pubDate>Tue, 13 Jan 2026 15:32:26 +0000</pubDate>
      <link>https://forem.com/hritickjaiswal/improving-accessibility-dropdown-4cl8</link>
      <guid>https://forem.com/hritickjaiswal/improving-accessibility-dropdown-4cl8</guid>
      <description>&lt;p&gt;I recently set out to improve my accessibility skills by doing something practical:&lt;br&gt;
&lt;strong&gt;building accessible UI primitives from scratch.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;And so today I targeted custom Dropdown / Select (not the native )&lt;/p&gt;
&lt;h2&gt;
  
  
  Why a Custom Dropdown?
&lt;/h2&gt;

&lt;p&gt;Because it is deceptively simple.&lt;/p&gt;

&lt;p&gt;Visually, it’s trivial.&lt;br&gt;
Behaviorally and accessibly, it’s one of the most commonly broken components on the web.&lt;/p&gt;

&lt;p&gt;If you can build an accessible custom select, you are forced to understand:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Focus management&lt;/li&gt;
&lt;li&gt;Keyboard-first interaction&lt;/li&gt;
&lt;li&gt;ARIA behavior, not just attributes&lt;/li&gt;
&lt;li&gt;Event scoping and containment&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  First Iteration Requirements
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Keyboard navigation&lt;/li&gt;
&lt;li&gt;ARIA Must Match the Actual Interaction Model&lt;/li&gt;
&lt;li&gt;Keyboard Handling Must Be Scoped&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Implementation
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { useEffect, useRef, useState } from "react";
import styles from "./style.module.css";

interface OptionType {
  label: string;
  value: string;
}

interface DropdownProps {
  value: string;
  options: Array&amp;lt;OptionType&amp;gt;;
  onChangeHandler: (val: string) =&amp;gt; void;
}

function Dropdown({ onChangeHandler, options, value }: DropdownProps) {
  const [show, setShow] = useState(false);
  const [activeIndex, setActiveIndex] = useState(-1);

  const wrapperRef = useRef&amp;lt;HTMLDivElement&amp;gt;(null);

  function handleSelectBtnClick() {
    if (!show) {
      setActiveIndex(options.findIndex((option) =&amp;gt; option.value === value));
    }

    setShow((prev) =&amp;gt; !prev);
  }

  function handleOptionClick(e: MouseEvent, val: string) {
    e.preventDefault();

    onChangeHandler(val);
    setShow(false);
  }

  function blurHandler() {
    setShow(false);
  }

  useEffect(() =&amp;gt; {
    const keyDownHandler = (e: KeyboardEvent) =&amp;gt; {
      const { key } = e;

      if (key === "ArrowUp") {
        setActiveIndex((prev) =&amp;gt; {
          return prev - 1 &amp;gt;= 0 ? prev - 1 : 0;
        });
      } else if (key === "ArrowDown") {
        setActiveIndex((prev) =&amp;gt; {
          return prev + 1 &amp;lt; options.length ? prev + 1 : options.length - 1;
        });
      } else if (key === "Enter" &amp;amp;&amp;amp; activeIndex &amp;gt;= 0) {
        onChangeHandler(options[activeIndex].value);
      } else if (key === "Escape") {
        setShow(false);
      } else if (
        key.toLowerCase().charCodeAt(0) &amp;gt;= 97 &amp;amp;&amp;amp;
        key.toLowerCase().charCodeAt(0) &amp;lt;= 122
      ) {
        const index = options.findIndex(
          (option) =&amp;gt; option.value[0].toLowerCase() === key.toLowerCase()
        );

        if (index &amp;gt;= 0) {
          setActiveIndex(index);
        }
      }
    };

    if (show) {
      wrapperRef.current?.addEventListener("keydown", keyDownHandler);
    }

    return () =&amp;gt; {
      if (show)
        wrapperRef.current?.removeEventListener("keydown", keyDownHandler);
    };
  }, [show, options, activeIndex, onChangeHandler]);

  return (
    &amp;lt;div ref={wrapperRef} className={styles.wrapper}&amp;gt;
      &amp;lt;button
        onClick={handleSelectBtnClick}
        className={styles.selectBtn}
        onBlur={blurHandler}
        aria-haspopup="listbox"
        aria-expanded={show}
        aria-controls="dropdown-listbox"
        aria-activedescendant={
          show &amp;amp;&amp;amp; activeIndex &amp;gt;= 0 ? `option-${activeIndex}` : undefined
        }
      &amp;gt;
        {value ? value : "Select an option"}
      &amp;lt;/button&amp;gt;

      &amp;lt;ul
        role="listbox"
        id="dropdown-listbox"
        className={`${styles.optionList} ${!show ? styles.hidden : ""}`}
      &amp;gt;
        {options.map(({ label, value }, i) =&amp;gt; (
          &amp;lt;li
            id={`option-${i}`}
            onMouseDown={(e) =&amp;gt; handleOptionClick(e, value)}
            className={`${styles.listItem} ${
              i === activeIndex ? styles.active : ""
            }`}
            key={value}
            aria-selected={i === activeIndex}
            role="option"
          &amp;gt;
            {label}
          &amp;lt;/li&amp;gt;
        ))}
      &amp;lt;/ul&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}

export default Dropdown;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Styles&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;.listItem.active {
  background-color: green;
  color: #fff;
  font-weight: 600;
}

.selectBtn:focus {
  outline-offset: 4px;
}

.hidden {
  display: none;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Key Learnings
&lt;/h2&gt;

&lt;p&gt;This was the most practical, hands-on learning.&lt;/p&gt;

&lt;p&gt;The problem&lt;br&gt;
When clicking an option:&lt;br&gt;
blur fires&lt;br&gt;
Then click fires&lt;/p&gt;

&lt;p&gt;And the &lt;strong&gt;dropdown closes before selection happens&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Visually, everything looks fine. &lt;strong&gt;Functionally, selection can fail.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The fix&lt;/p&gt;

&lt;p&gt;Understanding event priority changed everything:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;onMouseDown fires before blur&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;onClick fires after blur&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;By handling &lt;strong&gt;selection on onMouseDown&lt;/strong&gt;, I was able to:&lt;/p&gt;

&lt;p&gt;Capture the user’s intent&lt;br&gt;
Prevent premature close&lt;br&gt;
Preserve correct keyboard and mouse behavior&lt;/p&gt;

&lt;p&gt;This wasn’t about hacks — it was about understanding how the browser processes events.&lt;/p&gt;

</description>
      <category>a11y</category>
      <category>frontend</category>
      <category>webdev</category>
      <category>javascript</category>
    </item>
    <item>
      <title>[Boost]</title>
      <dc:creator>hritickjaiswal</dc:creator>
      <pubDate>Tue, 13 Jan 2026 15:12:44 +0000</pubDate>
      <link>https://forem.com/hritickjaiswal/-2oeo</link>
      <guid>https://forem.com/hritickjaiswal/-2oeo</guid>
      <description>&lt;div class="ltag__link"&gt;
  &lt;a href="/hritickjaiswal" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__pic"&gt;
      &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F548615%2F4c3dc8fb-5c76-4879-8fa5-496d1b4fb6d6.png" alt="hritickjaiswal"&gt;
    &lt;/div&gt;
  &lt;/a&gt;
  &lt;a href="https://dev.to/hritickjaiswal/improving-accessibility-modal-3bof" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__content"&gt;
      &lt;h2&gt;Improving Accessibility – Modal&lt;/h2&gt;
      &lt;h3&gt;hritickjaiswal ・ Jan 6&lt;/h3&gt;
      &lt;div class="ltag__link__taglist"&gt;
        &lt;span class="ltag__link__tag"&gt;#a11y&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#frontend&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#react&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#nextjs&lt;/span&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;


</description>
      <category>a11y</category>
      <category>frontend</category>
      <category>react</category>
      <category>nextjs</category>
    </item>
    <item>
      <title>Improving Accessibility – Modal</title>
      <dc:creator>hritickjaiswal</dc:creator>
      <pubDate>Tue, 06 Jan 2026 15:32:19 +0000</pubDate>
      <link>https://forem.com/hritickjaiswal/improving-accessibility-modal-3bof</link>
      <guid>https://forem.com/hritickjaiswal/improving-accessibility-modal-3bof</guid>
      <description>&lt;p&gt;This year, I decided to intentionally improve how I approach accessibility in UI components.&lt;/p&gt;

&lt;p&gt;Instead of chasing a “perfect” implementation, I’m focusing on iterative improvements—build, test, learn, and refine. In my experience, this leads to better engineering outcomes than over-designing upfront.&lt;/p&gt;

&lt;p&gt;I started with a modal, since it’s common, deceptively simple, and easy to get wrong.&lt;/p&gt;

&lt;h3&gt;
  
  
  First Iteration Requirements
&lt;/h3&gt;

&lt;p&gt;For the first pass, these were my non-negotiables:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Focus trapping (Tab / Shift + Tab)&lt;/li&gt;
&lt;li&gt;Focus restoration on close&lt;/li&gt;
&lt;li&gt;Move focus into the modal on open&lt;/li&gt;
&lt;li&gt;role="dialog" and aria-modal="true"&lt;/li&gt;
&lt;li&gt;Background inertness (inert / aria-hidden)&lt;/li&gt;
&lt;li&gt;Escape key handling&lt;/li&gt;
&lt;li&gt;Screen reader announcement timing&lt;/li&gt;
&lt;li&gt;Visible focus outlines via CSS&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Implementation
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { useEffect, useRef, useState, type ReactNode } from "react";

import styles from "./style.module.css";
import { createPortal } from "react-dom";

interface ModalProps {
  children: ReactNode;
  open: boolean;
  onClose: () =&amp;gt; void;
}

function Modal({ children, onClose, open }: ModalProps) {
  const modalRef = useRef&amp;lt;HTMLDivElement&amp;gt;(null);
  const bgFocusElement = useRef&amp;lt;HTMLButtonElement&amp;gt;(null);

  useEffect(() =&amp;gt; {
    const keydownHandler = (e: KeyboardEvent) =&amp;gt; {
      const { key } = e;
      const focusableElements = modalRef.current?.querySelectorAll(
        'button, a, [href], input,textarea, select, [tabindex]:not([tabindex = "-1"])'
      );

      if (key === "Tab" &amp;amp;&amp;amp; focusableElements?.length) {
        const firstFocusableElement = focusableElements[0];
        const lastFocusableElement =
          focusableElements[focusableElements.length - 1];

        if (e.shiftKey) {
          if (document.activeElement === firstFocusableElement) {
            e.preventDefault();
            (lastFocusableElement as HTMLButtonElement).focus();
          }
        } else {
          if (document.activeElement === lastFocusableElement) {
            e.preventDefault();
            (firstFocusableElement as HTMLButtonElement).focus();
          }
        }
      } else if (key === "Escape") {
        onClose();
      }
    };

    if (open) {
      document.addEventListener("keydown", keydownHandler);
      const focusableElements = modalRef.current?.querySelectorAll(
        'button, a, [href], input,textarea, select, [tabindex]:not([tabindex = "-1"])'
      );

      bgFocusElement.current = document.activeElement as HTMLButtonElement;

      if (focusableElements?.length) {
        (focusableElements[0] as HTMLButtonElement)?.focus();
      }
    }

    return () =&amp;gt; {
      if (open) {
        document.removeEventListener("keydown", keydownHandler);
      }
    };
  }, [open]);

  useEffect(() =&amp;gt; {
    if (open) {
      const rootElement = document.getElementById("root");

      if (rootElement) {
        rootElement.inert = true;
      }

      document.body.style.overflow = "hidden";
    } else {
      const rootElement = document.getElementById("root");

      if (rootElement) {
        rootElement.inert = false;
      }

      document.body.style.overflow = "";

      if (bgFocusElement.current) {
        bgFocusElement.current.focus();
        bgFocusElement.current = null;
      }
    }

    return () =&amp;gt; {
      const rootElement = document.getElementById("root");
      if (rootElement) {
        rootElement.inert = false;
      }
      document.body.style.overflow = "";
    };
  }, [open]);

  if (!open) {
    return null;
  }

  return createPortal(
    &amp;lt;section
      onClick={onClose}
      aria-modal="true"
      role="dialog"
      className={styles.modalContainer}
      ref={modalRef}
      aria-label="Modal-Container"
    &amp;gt;
      &amp;lt;div
        onClick={(e) =&amp;gt; e.stopPropagation()}
        className={styles.childrenContainer}
        aria-label="Modal-Child-Container"
      &amp;gt;
        {children}
      &amp;lt;/div&amp;gt;
    &amp;lt;/section&amp;gt;,
    document.body
  );
}

function ModalParent() {
  const [open, setOpen] = useState(false);

  const openModal = () =&amp;gt; {
    setOpen(true);
  };

  const closeModal = () =&amp;gt; {
    setOpen(false);
  };

  return (
    &amp;lt;article&amp;gt;
      &amp;lt;button className={styles.btn} onClick={openModal}&amp;gt;
        ModalParent
      &amp;lt;/button&amp;gt;
      &amp;lt;div
        style={{
          width: 400,
          height: 1300,
          backgroundColor: "red",
        }}
      &amp;gt;&amp;lt;/div&amp;gt;

      &amp;lt;Modal open={open} onClose={closeModal}&amp;gt;
        &amp;lt;div&amp;gt;
          &amp;lt;h1&amp;gt;Modal&amp;lt;/h1&amp;gt;

          &amp;lt;button className={styles.btn}&amp;gt;Modal Button&amp;lt;/button&amp;gt;

          &amp;lt;input type="text" /&amp;gt;
        &amp;lt;/div&amp;gt;
      &amp;lt;/Modal&amp;gt;
    &amp;lt;/article&amp;gt;
  );
}

export default ModalParent;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Styles (Focus Visibility Matters)
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;.modalContainer {
  position: fixed;
  inset: 0;
  display: flex;
  justify-content: center;
  align-items: center;
  background: rgba(0, 0, 0, 0.5);
}

.childrenContainer {
  background: #fff;
  padding: 1rem;
  border-radius: 4px;
  max-width: 768px;
  width: 95%;
}

button:focus,
input:focus {
  outline: 2px solid orange;
  outline-offset: 4px;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Key Learnings
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Focus management is not optional&lt;/li&gt;
&lt;li&gt;Background inertness dramatically improves keyboard and screen reader behavior&lt;/li&gt;
&lt;li&gt;Native semantics + minimal ARIA works better than over-annotating&lt;/li&gt;
&lt;li&gt;Visual focus indicators matter more than expected&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Hope anyone who reads it finds it useful and any suggestions for my next iteration is most welcome.&lt;/p&gt;

</description>
      <category>a11y</category>
      <category>frontend</category>
      <category>react</category>
      <category>nextjs</category>
    </item>
    <item>
      <title>Event Bubbling and Delegation in JavaScript for beginners by a beginner.</title>
      <dc:creator>hritickjaiswal</dc:creator>
      <pubDate>Fri, 11 Feb 2022 07:44:32 +0000</pubDate>
      <link>https://forem.com/hritickjaiswal/event-bubbling-and-delegation-in-javascript-for-beginners-by-a-beginner-3m6k</link>
      <guid>https://forem.com/hritickjaiswal/event-bubbling-and-delegation-in-javascript-for-beginners-by-a-beginner-3m6k</guid>
      <description>&lt;p&gt;Event Bubbling and delegation is a very common topic asked in &lt;strong&gt;JavaScript interviews&lt;/strong&gt; but before knowing what they are we first need to have a basic concept about events in JavaScript.&lt;/p&gt;

&lt;p&gt;When we say events we usually mean HTML events and what we mean by that, is some change occurred in one of the HTML elements present on the page. Some e.g. are&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;An HTML input field was changed&lt;/li&gt;
&lt;li&gt;An HTML button was clicked&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;and often times when such an event occurs the developers want to perform specific actions in response to that event like for e.g&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Check the value inside the input element is valid or not&lt;/li&gt;
&lt;li&gt;Toggle a modal &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;and such. So to accomplish these things we developers use EventListeners , these event listeners helps us to execute a callback function in response to an event. e.g.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;document.getElementById("btn").addEventListener("click", sayHello);

function sayHello() { // Callback function
   console.log("Hello");
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So great, now we have the power to response to events &lt;br&gt;
BUT &lt;br&gt;
there is this one problem with event listeners they consume memory. So if you go crazy with them it's gonna affect the performance of the site.&lt;/p&gt;

&lt;p&gt;So to solve the problem we can use Event Delegation and Event Bubbling.&lt;/p&gt;

&lt;p&gt;Suppose you have your HTML code kinda like this&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html lang="en"&amp;gt;
  &amp;lt;head&amp;gt;
    &amp;lt;meta charset="UTF-8" /&amp;gt;
    &amp;lt;meta http-equiv="X-UA-Compatible" content="IE=edge" /&amp;gt;
    &amp;lt;meta name="viewport" content="width=device-width, initial-scale=1.0" /&amp;gt;
    &amp;lt;title&amp;gt;Event Bubbling&amp;lt;/title&amp;gt;
    &amp;lt;link rel="stylesheet" href="./style.css" /&amp;gt;
  &amp;lt;/head&amp;gt;
  &amp;lt;body&amp;gt;
    &amp;lt;div class="grid"&amp;gt;
      &amp;lt;div class="box"&amp;gt;&amp;lt;/div&amp;gt;
      &amp;lt;div class="box"&amp;gt;&amp;lt;/div&amp;gt;
      &amp;lt;div class="box"&amp;gt;&amp;lt;/div&amp;gt;
      &amp;lt;div class="box"&amp;gt;&amp;lt;/div&amp;gt;
      &amp;lt;div class="box"&amp;gt;&amp;lt;/div&amp;gt;
      &amp;lt;div class="box"&amp;gt;&amp;lt;/div&amp;gt;
      &amp;lt;div class="box"&amp;gt;&amp;lt;/div&amp;gt;
      &amp;lt;div class="box"&amp;gt;&amp;lt;/div&amp;gt;
      &amp;lt;div class="box"&amp;gt;&amp;lt;/div&amp;gt;
      &amp;lt;div class="box"&amp;gt;&amp;lt;/div&amp;gt;
      &amp;lt;div class="box"&amp;gt;&amp;lt;/div&amp;gt;
      &amp;lt;div class="box"&amp;gt;&amp;lt;/div&amp;gt;
      &amp;lt;div class="box"&amp;gt;&amp;lt;/div&amp;gt;
      &amp;lt;div class="box"&amp;gt;&amp;lt;/div&amp;gt;
      &amp;lt;div class="box"&amp;gt;&amp;lt;/div&amp;gt;
      &amp;lt;div class="box"&amp;gt;&amp;lt;/div&amp;gt;
      &amp;lt;div class="box"&amp;gt;&amp;lt;/div&amp;gt;
      &amp;lt;div class="box"&amp;gt;&amp;lt;/div&amp;gt;
      &amp;lt;div class="box"&amp;gt;&amp;lt;/div&amp;gt;
      &amp;lt;div class="box"&amp;gt;&amp;lt;/div&amp;gt;
      &amp;lt;div class="box"&amp;gt;&amp;lt;/div&amp;gt;
      &amp;lt;div class="box"&amp;gt;&amp;lt;/div&amp;gt;
      &amp;lt;div class="box"&amp;gt;&amp;lt;/div&amp;gt;
      &amp;lt;div class="box"&amp;gt;&amp;lt;/div&amp;gt;
      &amp;lt;div class="box"&amp;gt;&amp;lt;/div&amp;gt;
      &amp;lt;div class="box"&amp;gt;&amp;lt;/div&amp;gt;
      &amp;lt;div class="box"&amp;gt;&amp;lt;/div&amp;gt;
      &amp;lt;div class="box"&amp;gt;&amp;lt;/div&amp;gt;
      &amp;lt;div class="box"&amp;gt;&amp;lt;/div&amp;gt;
      &amp;lt;div class="box"&amp;gt;&amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
    &amp;lt;script src="./index.js"&amp;gt;&amp;lt;/script&amp;gt;
  &amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And now you want to perform some action when a "box" is clicked, in the above code there are 30 box divs so instead on attaching eventListeners to all 30 what we can do is delegate those events &lt;br&gt;
&lt;strong&gt;by attaching a single event listener on the parent div "grid".&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const grid = document.addEventListener(".grid");

grid.addEventListener("click", (eventObj) =&amp;gt; {
  console.log(eventObj)
});

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;BUT we wanted to target the box right ?? So what to do now.&lt;br&gt;
The answer is (drum rolls) event object "eventObj" , the callback function for an event Listener passes an event object which has several methods and properties which are very handy and can help us accomplish our task.&lt;/p&gt;

&lt;p&gt;And this is it . Simple, right ?&lt;/p&gt;

&lt;p&gt;So if you guys find the post helpful like and share and if I made a mistake somewhere please let me know.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>beginners</category>
      <category>tutorial</category>
      <category>html</category>
    </item>
    <item>
      <title>Debouncing for beginners by a beginner</title>
      <dc:creator>hritickjaiswal</dc:creator>
      <pubDate>Sat, 23 Oct 2021 11:41:00 +0000</pubDate>
      <link>https://forem.com/hritickjaiswal/debouncing-for-beginners-by-a-beginner-5bhg</link>
      <guid>https://forem.com/hritickjaiswal/debouncing-for-beginners-by-a-beginner-5bhg</guid>
      <description>&lt;p&gt;I am a beginner and I recently learned &lt;strong&gt;debouncing&lt;/strong&gt; and &lt;strong&gt;throttling&lt;/strong&gt; ,their usage and differences. &lt;/p&gt;

&lt;h1&gt;
  
  
  Debouncing
&lt;/h1&gt;

&lt;p&gt;Debouncing is a procedure in JavaScript(and possibly other programming languages) which helps us to reduce the number of function calls in response to an event.&lt;br&gt;
For understanding and simplicity sake let us suppose we need to make API calls for "keyup" events on an input element.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function makeAPICall() {
   //Making API Call
}

document.querySelector("input").addEventListener("keyup",makeAPICall)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If we use the above method and suppose I typed "Hritick" in 1 go in the input element a total of 7 API calls will be made (for each character typed) but most probably the application should only make the API call for "Hritick" or "Hrit" but we made an API call for each new character inserted which is very &lt;strong&gt;expensive&lt;/strong&gt;.&lt;br&gt;
So to solve this problem we can use Debouncing - &lt;strong&gt;by using debouncing we will make the API call only if the event (triggering the API call) hasn't taken place for a given iterval of time.&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function makeAPICall() {
   //Making API Call
}

function debounce(fn, delay) {
  let timer;
  return function () {
    clearTimeout(timer);
    timer = setTimeout(fn, delay);
  };
}

const optimisedFunction = debounce(makeAPICall, 500);

document.querySelector("input").addEventListener("keyup", optimisedFunction);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the above code we created an "optimisedFunction" for debouncing. Instead of directly making API calls on every "keyup" event we will make the API call &lt;strong&gt;500 milliseconds after every "keyup" event&lt;/strong&gt; and by using the concept of &lt;strong&gt;closures&lt;/strong&gt; we will &lt;strong&gt;cancel&lt;/strong&gt; every request for API call before the current "keyup" event by using &lt;strong&gt;clearTimeout(...)&lt;/strong&gt;.&lt;br&gt;
Thus making the API call only when the user has stopped typing or have slowed down typing.&lt;br&gt;
As the title said I am a beginner so if I made any mistakes or anyone has any question please comment me. And I'll make sure I correct or answer the comment.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>beginners</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Deep Freeze tutorial for Beginners by a Beginner</title>
      <dc:creator>hritickjaiswal</dc:creator>
      <pubDate>Fri, 08 Oct 2021 11:03:08 +0000</pubDate>
      <link>https://forem.com/hritickjaiswal/deep-freeze-tutorial-for-beginners-by-a-beginner-1glf</link>
      <guid>https://forem.com/hritickjaiswal/deep-freeze-tutorial-for-beginners-by-a-beginner-1glf</guid>
      <description>&lt;h1&gt;
  
  
  Deep Freeze tutorial for Beginners by a Beginner
&lt;/h1&gt;

&lt;p&gt;I got into web development just a few months back and a few days ago I came across a interview question asking to write code to deep freeze an object.&lt;/p&gt;

&lt;p&gt;But what is freezing an object ???&lt;/p&gt;

&lt;p&gt;Freezing an object means preventing new properties from being added to it, existing properties from being removed, prevents changing the enumerability, configurability, or writability of existing properties. Basically freezing an object means we &lt;strong&gt;cannot&lt;/strong&gt; add ,delete or change existing properties which otherwise would have been possible in javascript as it is a &lt;strong&gt;dynamic&lt;/strong&gt; language.&lt;/p&gt;

&lt;p&gt;To freeze objects in javascript we use Object.freeze() method&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const obj = {
  name: "Hritick",
};

console.log(obj.name); //* Prints -&amp;gt; Hritick

obj.name = "Hritick Jaiswal";
console.log(obj.name);//* Prints -&amp;gt; Hritick Jaiswal
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But now if we use Object.freeze&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Object.freeze(obj); //* Freezes the object
console.log(Object.isFrozen(obj)); //* Checks if an object is frozen or not

obj.name = "Hritick"; //* Changing values is ignored
console.log(obj);//* Prints -&amp;gt; { name: 'Hritick Jaiswal' }

obj.surname = "Jaiswal"; //* Adding values is ignored
console.log(obj);//* Prints -&amp;gt; { name: 'Hritick Jaiswal' }

delete obj.name; //* Deleting values is ignored
console.log(obj);//* Prints -&amp;gt; { name: 'Hritick Jaiswal' }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ok great but what the hell is deep freeze and if we have &lt;strong&gt;Object.freeze&lt;/strong&gt; and why do we need it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const obj = {
  name: "Hritick",
  address: {
    is: "Kolkata",
  },
};

console.log(obj.address.is);//* Prints -&amp;gt; Kolkata

Object.freeze(obj)

obj.address.is = "Hyderabad"; //! This is allowed
console.log(obj.address.is);//* Prints -&amp;gt; Hyderabad
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So why did Object.freeze did not work ???&lt;/p&gt;

&lt;p&gt;Well Object.freeze &lt;strong&gt;did work&lt;/strong&gt; &lt;br&gt;
It did "freeze" the properties of the object "obj" as the property "address" stores the memory location of the object { is:"Kolkata" } which &lt;strong&gt;cannot&lt;/strong&gt; be changed but Object.freeze works on the immediate layer only.&lt;/p&gt;

&lt;p&gt;Deep freezing is preventing from such cases. So what should we do ....&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function deepFreeze(object) {
  if (typeof object !== "object") return;

  for (const value of Object.values(object)) {
    deepFreeze(value);
  }

  Object.freeze(object);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The above code uses &lt;strong&gt;recursion&lt;/strong&gt; to basically freeze every object no matter at what level it is.&lt;/p&gt;

&lt;p&gt;And that's it , this is my first post here so if I made any mistakes or anyone has any suggestions &lt;strong&gt;please tell me&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>beginners</category>
      <category>tutorial</category>
    </item>
  </channel>
</rss>
