Getting Started
App Providerglobal.d.tsThemesTypes
cn(...args)Text ParserUnits Converters
AnchorAvatarBreadcrumbBurgerButtonCardCarouselCheckerCodeColor PickerCommandConfettiCopyButtonDouble Helix WordsFloating IndicatorGroupHighlight TextIndicatorInputKbdLabelLoaderPaginationPassword RequirementPolymorphic SlotProgressProseRatingRunning AreaScroll AreaSheetsSkeletonSliderStackSvgTableTabsTextareaTimelineTimesToasterTooltipTyping WordsTypography
useClickOutsideuseClipboarduseDeviceInfouseDialoguseDidUpdateuseDirectionuseDisclosureuseDocumentTitleuseDocumentVisibilityuseElementInfouseEyeDropperuseFetchuseFullscreenuseGeoLocationuseHotkeysuseHoveruseIduseImagePopupuseInputStateuseIntersectionuseIntervaluseIsomorphicEffectuseListStateuseLocalStorageuseMeasureScrollbaruseMediaQueryuseMergedRefuseMouseuseMoveuseMutationObserveruseNetworkuseOpenStateuseOrientationuseOSusePaginationusePWAInstalleruseRandomColorsuseReducedMotionuseReloaduseResizeObserveruseScrollIntoViewuseStateHistoryuseTimeoutuseTouchuseTriggeruseUncontrolleduseValidatedStateuseViewportSizeuseWindowEventuseWindowScroll
Docs
Web
Components
Loader
Label

A label element used to describe form controls and inputs.

Pagination

A content navigation control that enables paging through large datasets or items.


Edit this page on GitHub
  • Started
  • Utilities
  • Configuration
  • Components
  • Hooks
  • Examples
  • Github
  • Contributing
⌘+J

© 2025 oeri rights MIT


Designed in Earth-616

Built by oeri

Loader

A loading indicator component used to show that content or data is being loaded or processed.

Usage

Basic usage example to quickly see how the Loader works.

import { Loader } from "@/ui/loader";

export function LoaderDemo() {
  return <Loader size={48} />;
}

Properties

Interactive configurator to explore customization options for the Loader component.

Size
Color
import { Loader } from "@/ui/loader";
import { BrandOeriIcon } from "@/icons/*";

export function LoaderDemo() {
  return (
    <Loader variant="clockwise" size={14}>
      <BrandOeriIcon />
    </Loader>
  );
}

Options Usage

Composite

import { Loader } from "@/ui/loader";
 
export function LoaderDemo() {
  return <Loader variant="spinner" size={12} color="hsl(var(--constructive))" />;
}

Direct Component

import { LoaderSpinner } from "@/ui/loader";
 
export function LoaderDemo() {
  return <LoaderSpinner size={12} color="hsl(var(--constructive))" />;
}

API References

Styles API

type T = "saturation" | "body" | "slider" | "root" | "sliders" | "sliderOverlay" | "thumb" | "saturationOverlay" | "swatches" | "swatch" | "preview" | "alphaOverlay" | "shadowOverlay" | "colorOverlay" | "childrenOverlay";
Styles APITypeDefaultAnnotation
unstyled?Partial<Record<T, boolean>>falseif true, all default styles will be removed
classNames?Partial<Record<T, string>>undefinedSet className for T
styles?Partial<Record<T, CSSProperties>>undefinedSet style for T

Props API

Props APITypeDefaultAnnotation
variant?"spinner" | "orbit" | "clockwise" | "dots" | "buffer" | "rises"spinnervariant of loader
unstyled?keys object by variantundefined
classNames?keys object by variantundefined
styles?keys object by variantundefined
color?React.CSSProperties["color"] | "currentColor" | (string & {})hsl(var(--color))
size?string | number20px
duration?number1.2

Source Codes

Full working code example, including necessary markup and styles. You can copy and paste this code directly to start using the component immediately.

loader.tsx
globals.css
/* loader */ @layer base { .stylelayer-loader { &[data-loader="spinner"] { @apply relative flex items-center justify-center flex-nowrap text-transparent appearance-none size-[--spinner-size,20px] min-h-[--spinner-size,20px] min-w-[--spinner-size,20px] max-h-[--spinner-size,20px] max-w-[--spinner-size,20px]; & [data-spinner="bar"] { @apply h-[8%] w-[24%] absolute rounded-full bg-[--spinner-color,#757575] [animation:spinloader_var(--spinner-duration)_linear_var(--child-delay)_infinite]; } } &[data-loader="orbit"] { @apply relative flex flex-wrap content-center items-center justify-center size-[calc(var(--orbit-size)*1.333)] [&>svg]:size-[calc(var(--orbit-size)*1.111)] [&>svg]:text-[--orbit-color,#757575]; & [data-orbit="inner"] { @apply absolute animate-[rotate_2s_linear_0s_infinite_normal_none_running]; } & [data-orbit="orbit"] { @apply absolute size-[--orbit-size] bg-[--orbit-color,#757575] rounded-full animate-[transformSpare_var(--orbit-duration)_linear_infinite_none_running] first:top-0 first:right-0 first:[--from:scale(0.5)] first:[--to:scale(0)] last:bottom-0 last:left-0 last:[--from:scale(0)] last:[--to:scale(0.5)] last:[animation-direction:reverse]; } } &[data-loader="clockwise"] { @apply relative flex flex-wrap content-center items-center justify-center size-[--clockwise-size] bg-transparent rounded-full outline [outline-width:--outline] outline-offset-[--outline] outline-[--clockwise-color,#757575] [--outline:calc(var(--clockwise-size)/24)]; & [data-clockwise="clockwise"] { @apply absolute size-full rounded-full flex flex-wrap content-center items-center justify-center [--space:calc((var(--clockwise-size)*4.17)/100)] [--initial-size:calc((var(--clockwise-size)/2)+var(--space))] [--w:calc((var(--clockwise-size)*8.334)/100)] [--top:calc(var(--initial-size)-var(--h))] [animation:rotate_var(--duration,var(--clockwise-duration))_linear_var(--clockwise-delay,0s)_infinite_var(--clockwise-property,normal)_forwards_running]; &:first-child { @apply [--h:calc(var(--initial-size)-var(--w))] [--duration:calc(var(--clockwise-duration)*6)]; } &:last-child { @apply [--h:calc((var(--initial-size)))]; } &::before { @apply absolute z-[1] rounded-full content-[''] top-[--top] bg-[--clockwise-color,#757575] h-[--h] w-[--w]; } } } &[data-loader="dots"] { @apply flex flex-wrap flex-row items-center justify-between content-center h-[calc(var(--dots-size)/2)] w-[--dots-size]; & [data-dots="dots"] { @apply inline-block bg-[--dots-color,#757575] size-[calc(var(--dots-size)/5)] rounded-full animate-[dotsloader_var(--dots-duration)_linear_var(--dots-delay)_infinite_normal_both_running]; } } &[data-loader="buffer"] { @apply relative [--from:--buffer-size] [--to:calc(var(--buffer-size)/4)] flex flex-nowrap flex-row items-center justify-between size-[--buffer-size]; & [data-buffer="buffer"] { @apply inline-block w-[calc(var(--buffer-size)/8.75)] h-[calc(var(--buffer-size)/1)] bg-[--buffer-color,#757575] rounded-full [animation:heightSpare_var(--buffer-duration)_ease_var(--buffer-delay)_infinite_normal_both_running]; } } &[data-loader="rises"] { @apply relative flex items-center justify-center size-[--rises-size]; &::before, &::after { @apply content-[''] absolute size-full border-solid border-[--rises-color,#757575] rounded-full [animation:rotate_var(--rises-duration)_linear_var(--rises-delay,0s)_infinite_var(--rises-property)_forwards_running]; } &::before { @apply size-full border-[calc(var(--rises-size)/8)] border-t-transparent [--rises-property:normal]; } &::after { @apply size-[60%] border-[calc(var(--rises-size)/12)] border-b-transparent [--rises-property:reverse] opacity-20; } } } @keyframes spinloader { from { opacity: 1; } to { opacity: 0.15; } } @keyframes rotate { to { transform: rotate(1turn); } } @keyframes heightSpare { from, to { height: var(--from); } 50% { height: var(--to); } } @keyframes transformSpare { from, to { transform: var(--from); } 50% { transform: var(--to); } } @keyframes transformEntire { from { transform: var(--from); } to { transform: var(--to); } } @keyframes dotsloader { 50% { transform: scale(0.5); opacity: 0; } 100% { transform: scale(1); opacity: 1; } } }