React Integration

FrontAlign is React-ready from the same frontalign package. You do not install a separate React package.

Use the CSS from frontalign/css and React hooks from frontalign/react.

Install

npm install frontalign 

Import

import 'frontalign/css';

import {
  useAccordion,
  useAlert,
  useAlertDismiss,
  useBadge,
  useCarousel,
  useCollapse,
  useDarkMode,
  useDrawer,
  useDropdown,
  useForm,
  useModal,
  useNavbar,
  usePopover,
  useRange,
  useSelect,
  useSkeleton,
  useSwiper,
  useTabview,
  useToast,
  useTooltip
} from 'frontalign/react';

Recommended App Setup

FrontAlign React hooks are client-side hooks. In Next.js, place them inside a client component.

'use client';

import {
  useAccordion,
  useAlertDismiss,
  useCollapse,
  useDrawer,
  useDropdown,
  useForm,
  useNavbar,
} from "frontalign/react";

export function FrontAlignProvider({ children }) {
  useNavbar();
  useDrawer();
  useDropdown();
  useCollapse();
  useAccordion();
  useAlertDismiss();
  useForm();

return children;
}

Then wrap your app:

// app/layout.jsx
import { FrontAlignProvider } from './providers';

export default function RootLayout({ children }) {
  return (
    <html lang="en">
      <body>
        <FrontAlignProvider>
          {children}
        </FrontAlignProvider>
      </body>
    </html>
  );
}

Delegated Hooks

These hooks initialize FrontAlign DOM behavior globally and automatically clean up on unmount.

'use client';

import {
  useNavbar,
  useDrawer,
  useDropdown,
  useCollapse,
  useAccordion,
  useAlertDismiss,
  useForm,
} from "frontalign/react";

export function UIEngine() {
  useNavbar();
  useDrawer();
  useDropdown();
  useCollapse();
  useAccordion();
  useAlertDismiss();
  useForm();

return null;
}

Delegated Hook Reference

HookPurposeReturn
useNavbar()Responsive navbar toggle behaviorvoid
useDrawer()Drawer open, close, backdrop, Escape key and focus trapvoid
useDropdown()Dropdown click, hover and Escape behaviorvoid
useCollapse()Collapse and accordion-group behaviorvoid
useAccordion()Accordion open and close behaviorvoid
useAlertDismiss()Dismissible static alertsvoid
useForm()Validation, file input helpers, floating labels and range helpersvoid

Navbar Example

'use client';

import { useNavbar } from 'frontalign/react';

export function NavbarExample() {
  useNavbar();

  return (
    <nav className="navbar">
      <a className="navbar-brand" href="/">FrontAlign</a>

      <button
        className="navbar-toggler"
        fa-toggle="navbar"
        data-target="#main-menu"
        aria-label="Toggle navigation"
      >
      </button>

      <div id="main-menu" className="navbar-menu">
      </div>
    </nav>
  );
}

Drawer Example

'use client';

import { useDrawer } from "frontalign/react";

export function DrawerExample() {
  useDrawer();

return (

<>
<button className="button is-primary" fa-toggle="drawer" data-target="#demo-drawer">
Open Drawer
</button>

      <div id="demo-drawer" className="drawer is-end">
        <div className="drawer-panel">
        </div>
      </div>
    </>

);
}

Dropdown

useDropdown() initializes dropdown click and hover interactions with accessibility support (aria-expanded, Escape key).

'use client';

import { useDropdown } from 'frontalign/react';

export function DropdownExample() {
  useDropdown();

  return (
    <div className="dropdown">
      <button className="button" fa-toggle="dropdown">
        Options
      </button>
      <div className="dropdown-menu">
        <a className="dropdown-item" href="#">Edit</a>
        <a className="dropdown-item" href="#">Duplicate</a>
        <hr className="dropdown-divider" />
        <a className="dropdown-item is-danger" href="#">Delete</a>
      </div>
    </div>
  );
}

Collapse

useCollapse() initializes collapsible elements with slide/fade animations and accordion group support.

'use client';

import { useCollapse } from "frontalign/react";

export function CollapseExample() {
  useCollapse();

return (

<>
<button className="button" fa-toggle="collapse" data-target="#my-collapse">
Toggle content
</button>

      <div id="my-collapse" className="collapse">
        <div className="collapse-body">
          <p>This content is toggled by <code>useCollapse()</code>.</p>
        </div>
      </div>
    </>

);
}

Accordion

useAccordion() initializes accessible accordion interactions with animated open/close behavior.

'use client';

import { useAccordion } from 'frontalign/react';

export function AccordionExample() {
  useAccordion();

  return (
    <div className="accordion"fa-component="accoridon">
      <div className="accordion-item">
        <button className="accordion-header">What is FrontAlign?</button>
        <div className="accordion-body">
          <p>FrontAlign is a UI component library with React integration out of the box.</p>
        </div>
      </div>
      <div className="accordion-item">
        <button className="accordion-header">How do I install it?</button>
        <div className="accordion-body">
          <p>Run <code>npm install frontalign</code> and import the CSS and hooks.</p>
        </div>
      </div>
    </div>
  );
}

Alert Dismiss

useAlertDismiss() initializes dismissible static alerts with persistence via localStorage and animation support.

'use client';

import { useAlertDismiss } from "frontalign/react";

export function AlertDismissExample() {
  useAlertDismiss();

return (

<div className="alert is-info" id="welcome-alert"fa-component="alert">
<p>Welcome! This alert can be dismissed.</p>
<button className="alert-close" aria-label="Close alert"></button>
</div>
);
}

Form

useForm() initializes form validation, file upload helpers, floating labels and range slider interactions.

'use client';

import { useForm } from 'frontalign/react';

export function FormExample() {
  useForm();

  return (
    <form className="form" fa-component="form"noValidate>
      <div className="form-group is-floating">
        <input id="email" type="email" className="form-input"data-rule="email" />
        <label htmlFor="email">Email address</label>
      </div>

      <div className="form-group">
        <input type="range" className="form-range"fa-component="range" min="0" max="100" defaultValue="40" />
      </div>

      <button type="submit" className="button is-primary">Submit</button>
    </form>
  );
}

Carousel

useCarousel(target, options) accepts either a React ref or a CSS selector string for target. Pass null to use the hook's own ref. Returns { ref, next, prev, go } for imperative control. The instance is automatically disposed on unmount.

'use client';

import { useCarousel } from 'frontalign/react';

export function CarouselExample() {
  const { ref, next, prev, go } = useCarousel(null, {
    mode: 'slide',
    loop: true,
    controls: true,
    pager: true,
    swipe: true,
    autoplay: {
      enabled: true,
      interval: 4000,
      pauseOnHover: true,
      pauseOnSwipe: true
    }
  });

  return (
    <section>
      <div className="carousel" ref={ref}>
        <div className="carousel-items">
          <div className="item">Slide 1</div>
          <div className="item">Slide 2</div>
          <div className="item">Slide 3</div>
        </div>
      </div>
    </section>
  );
}

Carousel with a Selector

If you don't need the ref (for example, the markup already exists outside React's control), pass a CSS selector as target instead of null.

'use client';

import { useCarousel } from 'frontalign/react';

export function CarouselSelectorExample() {
  const { next, prev } = useCarousel('#hero-carousel', { loop: true });

  return (
    <section>
      <div id="hero-carousel" className="carousel">
        <div className="carousel-items">
          <div className="item">Slide 1</div>
          <div className="item">Slide 2</div>
        </div>
      </div>
    </section>
  );
}

Tooltip

useTooltip(options) returns a ref. Attach it to the trigger element. The instance is automatically disposed on unmount.

'use client';

import { useTooltip } from 'frontalign/react';

export function TooltipExample() {
  const tipRef = useTooltip({
    message: 'Copy to clipboard',
    placement: 'top',
    trigger: 'hover',
    hasArrow: true
  });

  return (
    <button ref={tipRef} className="button is-primary">
      Copy
    </button>
  );
}

Popover

usePopover(options) returns a ref. Attach it to the trigger element. The instance is automatically disposed on unmount.

'use client';

import { usePopover } from 'frontalign/react';

export function PopoverExample() {
  const popoverRef = usePopover({
    title: 'Info',
    content: 'Popover content',
    placement: 'bottom',
    trigger: 'click',
    closeOnOutsideClick: true,
    closeOnEscape: true
  });

  return (
    <button ref={popoverRef} className="button is-primary">
      Open
    </button>
  );
}

Popover with Target Content

Use target to source the popover content from an existing element (for example a <template>) instead of passing content directly.

'use client';

import { usePopover } from 'frontalign/react';

export function PopoverTargetExample() {
  const popoverRef = usePopover({
    target: '#popover-template',
    placement: 'right',
    trigger: 'click'
  });

  return (
    <>
      <button ref={popoverRef} className="button">
        Show details
      </button>

      <template id="popover-template">
        <strong>Shipping</strong>
        <p>Orders ship within 2 business days.</p>
      </template>
    </>
  );
}

Tabview

useTabview() returns a ref. Attach it to the tab container. Handles tab switching, the animated underline, and ResizeObserver cleanup automatically.

'use client';

import { useTabview } from "frontalign/react";

export function TabviewExample() {
  const tabRef = useTabview();

  return (
    <>
      <div fa-component="tabview" ref={tabRef} className="tabview">
        <button className="tab-item is-active" data-tab="#panel-one">One</button>
        <button className="tab-item" data-tab="#panel-two">Two</button>
      </div>

      <div id="panel-one" className="tab-panel is-open">First panel</div>
      <div id="panel-two" className="tab-panel">Second panel</div>
    </>
  );
}

Swiper

useSwiper() returns a ref for pointer-based horizontal drag behavior. Pointer listeners are removed automatically on unmount.

'use client';

import { useSwiper } from 'frontalign/react';

export function SwiperExample() {
  const swiperRef = useSwiper();

  return (
    <div fa-component="swiper" className="swiper" ref={swiperRef}>
      <div className="swiper-wrapper">
        <article className="card">Card 1</article>
        <article className="card">Card 2</article>
        <article className="card">Card 3</article>
      </div>
    </div>
  );
}

Badge

useBadge(count) returns a ref and formats large numbers as 99+. The effect re-runs whenever count changes.

'use client';

import { useBadge } from "frontalign/react";

export function BadgeExample({ count = 128 }) {
  const badgeRef = useBadge(count);

  return (
    <span className="badge is-danger" data-count={count} ref={badgeRef}></span>
  );
}

Toast

useToast() does not return a ref — it returns status methods that create and show a toast directly.

'use client';

import { useToast } from 'frontalign/react';

export function ToastExample() {
  const toast = useToast();

  return (
    <button
      className="button is-success"
      onClick={() => toast.success('Profile updated successfully.')}
    >
      Save
    </button>
  );
}
MethodUsage
toast.success(message, options)Success toast
toast.danger(message, options)Danger toast
toast.warning(message, options)Warning toast
toast.info(message, options)Info toast
toast.show(options)Custom toast — pass message, status, position, etc. directly

Modal

useModal() does not return a ref — it returns promise-based helpers that mirror Modal.alert(), Modal.confirm(), Modal.custom(), and the Modal.queue API.

'use client';

import { useModal } from "frontalign/react";

export function ModalExample() {
  const { alert, confirm } = useModal();

  async function handleDelete() {
    const confirmed = await confirm({
      heading: 'Delete item?',
      content: 'This action cannot be undone.'
    });

    if (confirmed) {
      await alert({
        heading: 'Deleted',
        content: 'The item was deleted successfully.'
      });
    }
  }

  return (
    <button className="button is-danger" onClick={handleDelete}>
      Delete
    </button>
  );
}

Custom Modal

'use client';

import { useModal } from "frontalign/react";

export function CustomModalExample() {
  const { custom } = useModal();

  return (
    <button className="button" onClick={() => custom({ id: '#my-modal' })}>
      Open custom modal
    </button>
  );
}

Queued Modals

queue.alert() and queue.confirm() mirror alert() and confirm(), but wait their turn if another modal is already open or queued.

'use client';

import { useModal } from "frontalign/react";

export function QueuedModalExample() {
  const { queue } = useModal();

  async function runSteps() {
    await queue.alert({ heading: 'Step 1', content: 'First modal' });
    const proceed = await queue.confirm({ heading: 'Step 2', content: 'Continue?' });
    if (proceed) {
      await queue.alert({ heading: 'Step 3', content: 'All done.' });
    }
  }

  return (
    <button className="button" onClick={runSteps}>
      Start steps
    </button>
  );
}

Dynamic Alert

useAlert() does not return a ref — it returns status methods that insert an alert relative to a target selector. Use position to control whether it's inserted 'before' or 'after' the target.

'use client';

import { useAlert } from "frontalign/react";

export function AlertExample() {
  const alert = useAlert();

  return (
    <>
      <div id="form-message"></div>

      <button
        className="button is-primary"
        onClick={() => alert.success('#form-message', {
          message: 'Profile saved.',
          position: 'after'
        })}
      >
        Show Alert
      </button>
    </>
  );
}

Select

useSelect(target, options) accepts either a CSS selector string or a React ref pointing to a native <select> element for target. It does not return a ref — it returns { instance }, a mutable ref to the underlying Select instance. Use the onChange option to sync the selected value into React state.

'use client';

import { useState } from 'react';
import { useSelect } from 'frontalign/react';

export function SelectExample() {
  const [country, setCountry] = useState('');

  useSelect('#country', {
    data: [
      { value: 'az', name: 'Azerbaijan' },
      { value: 'tr', name: 'Turkey' },
    ],
    placeholder: 'Select country',
    onChange: setCountry,
  });

  return (
    <>
      <select id="country"></select>
      <p>Selected: {country || 'None'}</p>
    </>
  );
}

Select with a Ref

target also accepts a React ref instead of a selector — useful when the <select> is rendered conditionally or you want to avoid relying on a global id.

'use client';

import { useRef, useState } from 'react';
import { useSelect } from 'frontalign/react';

export function MultiSelectExample() {
  const selectRef = useRef(null);
  const [tags, setTags] = useState([]);

  useSelect(selectRef, {
    data: [
      { value: 'react', name: 'React' },
      { value: 'vue', name: 'Vue' },
    ],
    multiple: true,
    search: true,
    onChange: (value) => setTags(value.split(',')),
  });

  return <select ref={selectRef} />;
}

Skeleton

useSkeleton() returns { ref, show, hide, error, wrap }. Attach ref to the element you want to mask. If the element has a data-skeleton attribute, it activates automatically on mount.

'use client';

import { useEffect } from "react";
import { useSkeleton } from "frontalign/react";

export function SkeletonExample() {
  const { ref, wrap } = useSkeleton();

  useEffect(() => {
    wrap(async () => {
      await new Promise((resolve) => setTimeout(resolve, 800));
    }, {
      message: 'Could not load content',
      retry: true
    });
  }, []);

  return (
    <div ref={ref} data-skeleton-layout="card">
      <h3>Loaded content</h3>
      <p>This content is protected by the skeleton state.</p>
    </div>
  );
}

Manual show / hide / error

'use client';

import { useSkeleton } from "frontalign/react";

export function SkeletonManualExample() {
  const { ref, show, hide, error } = useSkeleton();

  return (
    <>
      <div ref={ref} data-skeleton-layout="list">
        <p>Content goes here.</p>
      </div>

      <button className="button" onClick={show}>Show</button>
      <button className="button" onClick={hide}>Hide</button>
      <button
        className="button is-danger"
        onClick={() => error({ message: 'Failed to load', retry: true })}
      >
        Trigger error
      </button>
    </>
  );
}

Skeleton Methods

MethodDescription
show()Activates the skeleton state on the ref element
hide()Removes the skeleton state
error(options)Shows an error state, with optional message and retry
wrap(asyncFn, options)Shows the skeleton, runs asyncFn, then hides on success or shows the error state on failure

Dark Mode

useDarkMode(options) does not return a ref — it returns { isDark }. Pass onChange in the options object to sync the theme into React state; the hook calls it internally whenever the theme changes.

'use client';

import { useState } from 'react';
import { useDarkMode } from 'frontalign/react';

export function DarkModeExample() {
  const [dark, setDark] = useState(false);

  const { isDark } = useDarkMode({
    autoCreateBtn: false,
    customBtn: '#theme-toggle',
    onChange: setDark
  });

  return (
    <button id="theme-toggle" className="button is-light-outline">
      {dark || isDark() ? 'Dark' : 'Light'}
    </button>
  );
}

Auto-Created Toggle Button

Omit customBtn and leave autoCreateBtn at its default to let FrontAlign mount a floating toggle button for you.

'use client';

import { useDarkMode } from 'frontalign/react';

export function DarkModeAutoExample() {
  useDarkMode();

  return null;
}

Range

useRange(options) mounts a FrontAlign Range instance on the wrapper element. Attach ref to the element carrying fa-component="range" — never to the <input type="range"> itself, since Range locates its input(s) by querying inside that wrapper. Returns { ref, value, getValue, setValue, refresh, instance }. The instance is automatically disposed on unmount.

Options are read once, at mount. tooltip, ticks, valueLabel, onInput, and onChange are captured the first time the component renders and are not reactive — passing a new value for any of them on a later render has no effect on the mounted instance. If a callback needs access to fresh state, use a ref to hold the latest value internally, or remount the component (e.g. with a changing key) instead of expecting the hook to pick up new options.

The hook calls Range.getOrCreateInstance() rather than new Range(), so it's safe under React's Strict Mode double-invocation in development — mounting twice reuses the existing instance instead of creating a duplicate.

'use client';

import { useRange } from 'frontalign/react';

export function VolumeSliderExample() {
  const range = useRange({
    valueLabel: (v) => `${v}%`,
    onChange: (value) => console.log('Committed value:', value),
  });

  return (
    <div fa-component="range" ref={range.ref}>
      <input type="range" min="0" max="100" defaultValue="50" />
    </div>
  );
}

Dual (Min/Max) Range

Pass two input[type="range"] elements inside the wrapper and Range automatically switches to dual mode: a connecting fill track renders between the thumbs, and value becomes a [min, max] array instead of a single number.

'use client';

import { useRange } from 'frontalign/react';

export function PriceRangeExample() {
  const range = useRange({
    valueLabel: (v) => `$${v}`,
    onChange: ([min, max]) => {
      fetchProducts({ minPrice: min, maxPrice: max });
    },
  });

  return (
    <div fa-component="range" ref={range.ref}>
      <input type="range" min="0" max="1000" defaultValue="200" />
      <input type="range" min="0" max="1000" defaultValue="800" />
    </div>
  );
}

Live Value in React State

value updates automatically on every input (drag) and change (release) event — no manual event wiring needed to reflect it in your UI.

'use client';

import { useRange } from 'frontalign/react';

export function RatingSliderExample() {
  const range = useRange();

  return (
    <div>
      <div fa-component="range" ref={range.ref}>
        <input type="range" min="1" max="10" defaultValue="5" />
      </div>
      <p>Selected rating: {range.value}</p>
    </div>
  );
}

Ticks from a Datalist

ticks (enabled by default) reads tick marks from a <datalist> referenced via the input's list attribute. Ticks only apply to single-input ranges, not dual ranges.

'use client';

import { useRange } from 'frontalign/react';

export function SizeSliderExample() {
  const range = useRange();

  return (
    <div fa-component="range" ref={range.ref}>
      <input type="range" min="0" max="3" step="1" list="sizes" defaultValue="1" />
      <datalist id="sizes">
        <option value="0" label="XS" />
        <option value="1" label="S" />
        <option value="2" label="M" />
        <option value="3" label="L" />
      </datalist>
    </div>
  );
}

Live Updates vs. Committed Changes (onInput vs onChange)

onInput fires on every drag/keyboard movement, onChange fires once when the value is committed (pointer release, blur, or keyup). Both are called with (value, index)index identifies which thumb moved (0 or 1) and is only meaningful for dual ranges; for single ranges it's always 0.

'use client';

import { useState } from 'react';
import { useRange } from 'frontalign/react';

export function LiveVsCommittedExample() {
  const [dragging, setDragging] = useState(null);

  const range = useRange({
    onInput: (value, index) => setDragging({ value, index }),
    onChange: (value, index) => {
      setDragging(null);
      console.log(`Thumb ${index} committed at`, value);
    },
  });

  return (
    <div>
      <div fa-component="range" ref={range.ref}>
        <input type="range" min="0" max="1000" defaultValue="200" />
        <input type="range" min="0" max="1000" defaultValue="800" />
      </div>
      {dragging && <p>Dragging thumb {dragging.index}: {dragging.value}</p>}
    </div>
  );
}

Reading the Value On Demand (getValue)

value is kept in sync with React state, but state updates are asynchronous — inside a synchronous callback like a submit handler, getValue() reads the current value straight from the Range instance instead of relying on a possibly-stale value closure.

'use client';

import { useRange } from 'frontalign/react';

export function SubmitOnDemandExample() {
  const range = useRange();

  const handleSubmit = () => {
    const current = range.getValue();
    saveFilter({ threshold: current });
  };

  return (
    <div>
      <div fa-component="range" ref={range.ref}>
        <input type="range" min="0" max="100" defaultValue="50" />
      </div>
      <button className="button is-primary" onClick={handleSubmit}>
        Apply
      </button>
    </div>
  );
}

Programmatic Control

Use setValue to update the slider imperatively (accepts a number for single ranges or [min, max] for dual ranges), and refresh after changing min/max/step/list attributes on the DOM directly — Range has no MutationObserver watching those attributes, so visuals go stale silently without it.

'use client';

import { useRange } from 'frontalign/react';

export function ResetButtonExample() {
  const range = useRange();

  return (
    <div>
      <div fa-component="range" ref={range.ref}>
        <input type="range" min="0" max="100" defaultValue="50" />
      </div>
      <button className="button" onClick={() => range.setValue(50)}>
        Reset
      </button>
    </div>
  );
}

Range Options

OptionTypeDefaultDescription
tooltipbooleantrueShows a floating tooltip with the current value while dragging or focused
ticksbooleantrueRenders tick marks from the input's linked <datalist>. Ignored for dual ranges
valueLabelstring | function | nullnullFormats the displayed value. A string uses as a placeholder; a function receives the raw value and returns the formatted label
onInputfunctionCalled on every drag/keyboard movement with the live value (and thumb index for dual ranges)
onChangefunctionCalled once the value is committed (pointer release, blur, or keyup)

Range Return Values

PropertyTypeDescription
refRefObjectAttach to the fa-component="range" wrapper element, not the <input>
valuenumber | number[]Live value, kept in sync with React state. A single number, or [min, max] for dual ranges
getValue()functionReads the current value directly from the instance
setValue(value)functionSets the value imperatively and re-renders the fill, tooltip, and aria-valuetext
refresh()functionRe-reads min/max/step/list from the DOM and re-renders ticks, fill, and tooltip position
instanceRange | nullThe underlying Range class instance, for advanced/imperative use

Complete Export Reference

ExportReturnsType
useAccordionvoidDelegated Hook
useAlert show, success, danger, warning, info Hook
useAlertDismissvoidDelegated Hook
useBadgerefHook
useCarousel ref, next, prev, go Hook
useCollapsevoidDelegated Hook
useDarkMode isDark Hook
useDrawervoidDelegated Hook
useDropdownvoidDelegated Hook
useFormvoidDelegated Hook
useModal alert, confirm, custom, queue Hook
useNavbarvoidDelegated Hook
usePopoverrefHook
useRange ref, value, getValue, setValue, refresh, instance Hook
useSelect instance Hook
useSkeleton ref, show, hide, error, wrap Hook
useSwiperrefHook
useTabviewrefHook
useToast success, danger, warning, info, show Hook
useTooltiprefHook

Important Notes

  • There is no separate package like frontalign-react.
  • Install only frontalign.
  • Import React hooks from frontalign/react.
  • Import CSS once from frontalign/css.
  • Hooks that return a ref (useTooltip, usePopover, useTabview, useSwiper, useBadge, the ref from useSkeleton, useCarousel, and useRange) must have that ref attached to a DOM element, or the underlying instance is never created.
  • useRange's ref belongs on the wrapper, not the <input type="range">Range finds its input(s) by querying inside the element the ref is attached to.
  • useRange's options are captured once, at mount. tooltip, ticks, valueLabel, onInput, and onChange won't update on re-render if you pass new values — remount (e.g. via key) if the underlying config genuinely needs to change.
  • useRange is Strict Mode safe. It mounts through Range.getOrCreateInstance(), so React's development-mode double-invocation reuses the existing instance instead of creating a second one.
  • Hooks that don't return a ref (useAlert, useToast, useModal, useDarkMode) work against a selector or globally — no ref attribute needed in JSX.
  • useCarousel and useSelect accept either a ref or a CSS selector string as their first argument — pick whichever fits how the markup is rendered.
  • useSelect returns { instance }, not a ref — bind the <select> element with a plain id (selector mode) or pass your own ref as the target argument (ref mode).
  • Do not manually create new Carousel(), new Toast(), new Modal(), new Popover(), or new Tooltip() in React pages when a hook exists.
  • Use client components in Next.js ('use client') because every hook depends on browser APIs after mount.

FrontAlign