本文へ移動
uiify

Spinner

読み込み状態を示すアニメーション付きインジケーター。

ブラウザー
Chrome 123+Firefox 121+Safari 17.5+
ティア
0
圧縮後のサイズ
447 B
強制レイアウトの上限
0 ms
axe の上限
0
SSR 対応
はい
公開された契約
テストハーネス

プレイグラウンド

Spinner

Sizes

Label

Loading content
Loading content

Value

import { Spinner } from "@gunkin/uiify";

export default function Example() {
  return <Spinner className="" label="Loading results" />;
}
@property --uiify-spinner-tail {
  syntax: "<angle>";
  inherits: false;
  initial-value: 0deg;
}

@property --uiify-spinner-length {
  syntax: "<angle>";
  inherits: false;
  initial-value: 36deg;
}

@layer components {
  [data-uiify-spinner] {
    position: relative;
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
    color: var(--primary);
  }

  [data-uiify-spinner] > [data-part="label"] {
    color: var(--foreground);
  }

  [data-uiify-spinner]::before {
    flex: none;
    display: block;
    inline-size: 2rem;
    block-size: 2rem;
    content: "";
    border-radius: 50%;
    background: conic-gradient(
      from calc(-90deg + var(--uiify-spinner-tail)),
      currentcolor 0 var(--uiify-spinner-length),
      transparent 0
    );
    mask: radial-gradient(farthest-side, transparent calc(100% - 4px), #000 calc(100% - 3px));
    animation:
      uiify-spinner-rotate 2s linear infinite,
      uiify-spinner-orbit 1.5s ease-in-out infinite;
  }

  [data-uiify-spinner][data-value]::before {
    --uiify-spinner-tail: 0deg;
    --uiify-spinner-length: calc(attr(data-value type(<number>), 0) * 3.6deg);

    background: conic-gradient(
      from 0deg,
      currentcolor 0 var(--uiify-spinner-length),
      color-mix(in oklch, currentcolor 20%, transparent) 0
    );
    animation: none;
  }

  [data-uiify-spinner][data-size="sm"]::before {
    inline-size: 1.25rem;
    block-size: 1.25rem;
    mask: radial-gradient(farthest-side, transparent calc(100% - 3px), #000 calc(100% - 1px));
  }

  [data-uiify-spinner][data-size="lg"]::before {
    inline-size: 6.25rem;
    block-size: 6.25rem;
    mask: radial-gradient(farthest-side, transparent calc(100% - 4px), #000 calc(100% - 3px));
  }

  [data-uiify-spinner][data-align="center"] {
    margin-inline: auto;
  }

  [data-uiify-spinner][data-align="fixed"],
  [data-uiify-spinner][data-align="absolute"] {
    inset-block-start: 50%;
    inset-inline-start: 50%;
    translate: -50% -50%;
  }

  [data-uiify-spinner][data-align="fixed"] {
    position: fixed;
  }

  [data-uiify-spinner][data-align="absolute"] {
    position: absolute;
  }

  @keyframes uiify-spinner-rotate {
    to {
      transform: rotate(360deg);
    }
  }

  @keyframes uiify-spinner-orbit {
    0% {
      --uiify-spinner-tail: 0deg;
      --uiify-spinner-length: 36deg;
    }

    50% {
      --uiify-spinner-tail: 90deg;
      --uiify-spinner-length: 324deg;
    }

    100% {
      --uiify-spinner-tail: 360deg;
      --uiify-spinner-length: 36deg;
    }
  }

  @media (prefers-reduced-motion: reduce) {
    [data-uiify-spinner]:not([data-value])::before {
      animation: uiify-spinner-orbit 2.5s ease-in-out infinite;
    }
  }
}
import { Spinner } from "@gunkin/uiify";

export default function Example() {
  return (
    <Spinner
      className={
        "relative inline-flex items-center [gap:0.5rem] text-primary [&>[data-part=label]]:text-foreground before:fl" +
        "ex-none before:block before:[inline-size:2rem] before:[block-size:2rem] before:content-[''] before:rounded-" +
        "[50%] before:[background:conic-gradient(_from_calc(-90deg_+_var(--uiify-spinner-tail)),_currentcolor_0_var(" +
        "--uiify-spinner-length),_transparent_0_)] before:[mask:radial-gradient(farthest-side,_transparent_calc(100%" +
        "_-_4px),_#000_calc(100%_-_3px))] before:[animation:uiify-spinner-rotate_2s_linear_infinite,_uiify-spinner-o" +
        "rbit_1.5s_ease-in-out_infinite] data-[value]:before:[--uiify-spinner-tail:0deg] data-[value]:before:[--uiif" +
        "y-spinner-length:calc(attr(data-value_type(<number>),_0)_*_3.6deg)] data-[value]:before:[background:conic-g" +
        "radient(_from_0deg,_currentcolor_0_var(--uiify-spinner-length),_color-mix(in_oklch,_currentcolor_20%,_trans" +
        "parent)_0_)] data-[value]:before:[animation:none] data-[size=sm]:before:[inline-size:1.25rem] data-[size=sm" +
        "]:before:[block-size:1.25rem] data-[size=sm]:before:[mask:radial-gradient(farthest-side,_transparent_calc(1" +
        "00%_-_3px),_#000_calc(100%_-_1px))] data-[size=lg]:before:[inline-size:6.25rem] data-[size=lg]:before:[bloc" +
        "k-size:6.25rem] data-[size=lg]:before:[mask:radial-gradient(farthest-side,_transparent_calc(100%_-_4px),_#0" +
        "00_calc(100%_-_3px))] data-[align=center]:[margin-inline:auto] data-[align=fixed]:[inset-block-start:50%] d" +
        "ata-[align=fixed]:[inset-inline-start:50%] data-[align=fixed]:[translate:-50%_-50%] data-[align=absolute]:[" +
        "inset-block-start:50%] data-[align=absolute]:[inset-inline-start:50%] data-[align=absolute]:[translate:-50%" +
        "_-50%] data-[align=fixed]:fixed data-[align=absolute]:absolute motion-reduce:[&:not([data-value])::before]:" +
        "[animation:uiify-spinner-orbit_2.5s_ease-in-out_infinite]"
      }
      label="Loading results"
    />
  );
}
/* app.css */

@layer reset, ui, app, tokens, base, demo, components, utilities, theme;

@import "@gunkin/uiify/styles/reset";

@import "@gunkin/uiify/components/behavior.css";

@import "@gunkin/uiify/styles/tokens";

@import "@gunkin/uiify/styles/tailwind";

@import "tailwindcss/utilities.css";

@theme { --spacing: 0.25rem; --font-weight-normal: 400; --font-weight-medium: 500; --font-weight-semibold: 600; --font-weight-bold: 700; }

@source inline('relative inline-flex items-center [gap:0.5rem] text-primary [&>[data-part=label]]:text-foreground before:flex-none before:block before:[inline-size:2rem] before:[block-size:2rem] before:rounded-[50%] before:[background:conic-gradient(_from_calc(-90deg_+_var(--uiify-spinner-tail)),_currentcolor_0_var(--uiify-spinner-length),_transparent_0_)] before:[mask:radial-gradient(farthest-side,_transparent_calc(100%_-_4px),_#000_calc(100%_-_3px))] before:[animation:uiify-spinner-rotate_2s_linear_infinite,_uiify-spinner-orbit_1.5s_ease-in-out_infinite] data-[value]:before:[--uiify-spinner-tail:0deg] data-[value]:before:[--uiify-spinner-length:calc(attr(data-value_type(<number>),_0)_*_3.6deg)] data-[value]:before:[background:conic-gradient(_from_0deg,_currentcolor_0_var(--uiify-spinner-length),_color-mix(in_oklch,_currentcolor_20%,_transparent)_0_)] data-[value]:before:[animation:none] data-[size=sm]:before:[inline-size:1.25rem] data-[size=sm]:before:[block-size:1.25rem] data-[size=sm]:before:[mask:radial-gradient(farthest-side,_transparent_calc(100%_-_3px),_#000_calc(100%_-_1px))] data-[size=lg]:before:[inline-size:6.25rem] data-[size=lg]:before:[block-size:6.25rem] data-[size=lg]:before:[mask:radial-gradient(farthest-side,_transparent_calc(100%_-_4px),_#000_calc(100%_-_3px))] data-[align=center]:[margin-inline:auto] data-[align=fixed]:[inset-block-start:50%] data-[align=fixed]:[inset-inline-start:50%] data-[align=fixed]:[translate:-50%_-50%] data-[align=absolute]:[inset-block-start:50%] data-[align=absolute]:[inset-inline-start:50%] data-[align=absolute]:[translate:-50%_-50%] data-[align=fixed]:fixed data-[align=absolute]:absolute motion-reduce:[&:not([data-value])::before]:[animation:uiify-spinner-orbit_2.5s_ease-in-out_infinite]');

@source inline("before:content-['']");

@layer base { :root { --uiify-control-text: var(--text-sm); } @media (pointer: coarse) { :root { --uiify-control-text: var(--text-base); } } }

/* Authored animation and registered properties used by the Tailwind skin.
 * Visual rules belong in utility recipes; only at-rule definitions live here. */
@layer components {
  @keyframes uiify-presence-in {
    from {
      opacity: 0;
      scale: var(--presence-scale);
    }
  }
}

@layer components {
  @keyframes uiify-presence-out {
    to {
      opacity: 0;
      scale: var(--presence-scale);
    }
  }
}

@property --uiify-spinner-tail {
  syntax: "<angle>";
  inherits: false;
  initial-value: 0deg;
}

@property --uiify-spinner-length {
  syntax: "<angle>";
  inherits: false;
  initial-value: 36deg;
}

@layer components {
  @keyframes uiify-spinner-rotate {
    to {
      transform: rotate(360deg);
    }
  }
}

@layer components {
  @keyframes uiify-spinner-orbit {
    0% {
      --uiify-spinner-tail: 0deg;
      --uiify-spinner-length: 36deg;
    }

    50% {
      --uiify-spinner-tail: 90deg;
      --uiify-spinner-length: 324deg;
    }

    100% {
      --uiify-spinner-tail: 360deg;
      --uiify-spinner-length: 36deg;
    }
  }
}

@layer components {
  @keyframes uiify-skeleton-pulse {
    50% {
      opacity: 0.45;
    }
  }
}

@layer components {
  @keyframes uiify-skeleton-shimmer {
    to {
      background-position: -200% 0;
    }
  }
}

@layer components {
  @keyframes uiify-skeleton-reveal {
    from {
      opacity: 0;
    }
    to {
      opacity: 1;
    }
  }
}

@property --candle-body-height {
  syntax: "<percentage>";
  inherits: true;
  initial-value: 0%;
}

@property --candle-body-top {
  syntax: "<percentage>";
  inherits: true;
  initial-value: 50%;
}

@property --candle-slot {
  syntax: "<percentage>";
  inherits: true;
  initial-value: 100%;
}

@property --candle-wick-height {
  syntax: "<percentage>";
  inherits: true;
  initial-value: 0%;
}

@property --candle-wick-top {
  syntax: "<percentage>";
  inherits: true;
  initial-value: 50%;
}

@property --candle-x {
  syntax: "<percentage>";
  inherits: true;
  initial-value: 0%;
}

@property --candle-scale {
  syntax: "<number>";
  inherits: true;
  initial-value: 1;
}

@property --line-y {
  syntax: "<percentage>";
  inherits: true;
  initial-value: 50%;
}

@property --line-prev-y {
  syntax: "<percentage>";
  inherits: true;
  initial-value: 50%;
}

@property --line-count {
  syntax: "<integer>";
  inherits: false;
  initial-value: 1;
}

@property --line-scale {
  syntax: "<number>";
  inherits: true;
  initial-value: 1;
}

@layer components {
  @keyframes uiify-otp-input-caret-blink {
    50% {
      opacity: 0;
    }
  }
}

API

Spinner

Prop型既定値説明
asElementType"div"as="span"などで、描画するルートタグを変更します。
classNamestring—クライアント側のスタイリング用に、クラス名をルート要素に転送します。
align"center" | "fixed" | "absolute"—スピナーを通常フロー、ビューポートへの固定、絶対配置のいずれかで配置します。
childrenReactNode—ステータス領域のアクセシブルな名前とともに描画する可視の内容。
valuenumber—0〜100の固定パーセント値。アニメーションを静的な円弧に置き換え、ロールを progressbar に切り替えます。
delaynumber0スピナーを表示するまでの待機時間(ミリ秒)。CSSで適用するためJavaScriptは不要です。
labelstring"Loading"ステータス領域が読み上げるアクセシブルな名前。
showLabelbooleanfalse可視のラベルを[data-part="label"]パーツとして描画します。
size"small" | "default" | "large""default"CSSのみのスピナーの直径を制御します。
renderReactElement | ((props, state) => ReactElement)—動作とアクセシビリティの接続を維持したまま、描画する要素を置き換えます。

キーボード

キー操作

アクセシビリティ

aria-label付きのrole="status"ライブリージョンを使います。視覚的インジケーターはCSSのみで、SVGを描画しません。

スタイリングのフック

data-uiify-spinnerdata-statedata-delaydata-valuedata-sizedata-aligndata-part="label"--uiify-spinner-tail--uiify-spinner-length
このページは機械翻訳によるもので、レビューは未実施です。