メインコンテンツへスキップ
menumenu tanreiexpand_morecontrasttranslate
tanreiexpand_more
はじめにWebReactSwiftUIJetpack ComposeFlutterReact Nativeプリセット自分のデザインシステムを作るAI エージェントと Tanrei を使うブランドデザイントークンデザインシステムとカスタマイズTheme Studioカラータイポグラフィアイコン形状エレベーションモーションインタラクション状態振る舞いレイアウトアクセシビリティ言葉カラートークンタイプスケールトークン形状トークンエレベーショントークンモーショントークン状態トークンスペースと寸法のトークンコンポーネントボタンメインアクションボタンセグメントボタンボタングループメニュー付きボタンツールバーコピーボタンチェックボックスラジオボタンスイッチチップメニュースライダースライダーフィールドカレンダーDateTimePickerフィールドグループラジオグループチェックボックスグループセレクトセレクトフィールドメニューバーファイルアップロードファイルピッカー 上級者向けドロップゾーンテキスト入力欄 上級者向けテキストフィールドチップフィールドフィールドコンボボックス 上級者向けコンボボックスフィールド数値入力欄 上級者向け数値フィールドカラー入力欄 上級者向けカラーフィールドワンタイムコード入力欄 上級者向けワンタイムコードフィールド埋め込みテキスト入力欄 上級者向けチャットコンポーザーカード区切り線モーダルレイヤー 上級者向けダイアログリストリスト行ボトムシートサイドシートカルーセルコードブロックテーブルポップオーバーステップアコーディオン説明リスト図アプリシェルページヘッダーツリービュータイムライン画像プレビューハンドルサイズ変更できる枠スクロールエリアバッジプログレスインジケーターメーターナンバーティッカーローディングインジケータースナックバーツールチップキーボードショートカットタグインラインコードコールアウトアバター空の状態タググループスケルトンファイル項目色見本マーキーテキストタブタブビューアプリバーナビゲーションバーナビゲーションドロワー検索リンクページネーション前後のリンクステッパーパンくずリスト目次リンクカードFAB メニューステータスTanrei の作り方コントリビューションライセンスと帰属
ホームコンポーネントスケルトン

スケルトン

読み込みのあいだ、コンテンツが入る場所に置くものです。これから来るものの形をした控えめなプレースホルダー(テキストの行、ブロック、円)が、コンテンツの入るボックスを確保するので、届いたときに何も動きません。

content_copyMarkdown で表示
カテゴリ
コミュニケーション
トークン
comp.skeleton
レベル
基礎

概要

Web
tanrei-skeleton
schedule
SwiftUI
TanreiSkeleton
radio_button_unchecked
Jetpack Compose
TanreiSkeleton
radio_button_unchecked
Flutter
TanreiSkeleton
radio_button_unchecked
React Native
Skeleton
radio_button_unchecked

生成元 docs/status/status.json, specs/skeleton.spec.json

読み込み中の人物の情報とカード

ビューは、コンテンツより先に表示されることがよくあります。スケルトンは、これから来るものの形を、それが入る場所に確保します。テキストの行、画像やコントロールのためのブロック、人物の画像のための円です。コンテンツが届くと、確保してあったボックスにそのまま入るので、見ていたものも、押そうとしていたものも動きません。

スケルトンは、領域とその中のプレースホルダーでできています。支援技術には領域が 1 回だけ伝わります(「プロジェクトを読み込み中」、ビジー状態)。プレースホルダーそのものは何も伝えません。表示までに少し待つので、短い待ち時間では形がちらつかず、正しい大きさの空白だけが見えます。

Web では <tanrei-skeleton> で、その中に class="tanrei-skeleton-placeholder" を付けた利用者側の要素を置きます。ほかのプラットフォームはまだ実装していません。

スケルトン、ローディングインジケーター、プログレスインジケーター、空の状態

「ここにはまだ何もない」に答えるコンポーネントは 4 つあります。どれを使うかは、待ち時間と、これから来るものについて何が分かっているかで決まります。

状況 使うもの 理由
形の分かっているコンテンツがビューの一部を埋める予定で、待ち時間が目に見えるほど長い。 スケルトン レイアウトを確保するので、届いたときに何も動きません。どこに何が来るかも示します。
長さの分からない短い待ち時間で、結果に確保すべき形がないか、小さい。ボタン、行、フィールド、ステータス行の中。保存や送信など、進行中の操作。 ローディングインジケーター 印が 1 つあれば足ります。1 行や 1 つのアイコンのための形は雑音です。
進み具合を測れるタスク、または何秒もかかるタスク。 プログレスインジケーター スケルトンは、どこまで進んだかを伝えません。数秒たつと、人はそれを知りたくなります。
何も来ない。リストが空、検索結果がない。 空の状態 スケルトンは、コンテンツが来ることを約束します。
読み込みに失敗した。 その場所に、エラーのステータスのコールアウト。 終わらないスケルトンは嘘になります。
コンテンツはすでにあり、スクリプト、画像、フォントだけが遅れている。 4 つのどれでもありません。そのコンポーネントが自分のボックスを確保します。 スケルトンはほかのコンポーネントの大きさを知りません。知っているのはそのコンポーネントです。

使い方

待ち時間が始まったら、コンテンツが入る場所にスケルトンを置き、終わったらコンテンツに置き換えます。何を読み込んでいるかを label で渡します。プレースホルダーは、このクラスを付けた利用者側の空の要素です。<span> はどこにでも置けます。別の <div> のようなブロックの子として置くときは、<div> でもかまいません。見出しや段落の中では <span> にします。

<link rel="stylesheet" href="@tanrei/web/skeleton.css" />

<!-- スケルトンの直接の子であるプレースホルダーは、間隔をあけて縦に並びます。 -->
<tanrei-skeleton label="プロジェクトを読み込み中">
  <span class="tanrei-skeleton-placeholder" data-shape="block" style="block-size: 160px"></span>
  <span class="tanrei-skeleton-placeholder" style="inline-size: 12.5rem"></span>
  <span class="tanrei-skeleton-placeholder" data-lines="3"></span>
</tanrei-skeleton>

<!-- それ以上のものは利用者側のレイアウトです。プレースホルダーは、コンテンツが入る場所に置きます。 -->
<tanrei-skeleton label="著者の情報を読み込み中">
  <div class="byline">
    <span class="tanrei-skeleton-placeholder" data-shape="circle" style="block-size: 40px"></span>
    <div>
      <h3><span class="tanrei-skeleton-placeholder" style="inline-size: 10rem"></span></h3>
      <p><span class="tanrei-skeleton-placeholder" data-lines="2"></span></p>
    </div>
  </div>
</tanrei-skeleton>

<!-- ページの最初の表示の一部で、サーバーが書き出すもの。すぐに表示され、スクリプトの前から正しく伝わります。 -->
<tanrei-skeleton show-delay="0" role="progressbar" aria-busy="true" aria-label="ダッシュボードを読み込み中">
  …
</tanrei-skeleton>
<noscript>ダッシュボードの表示には JavaScript が必要です。</noscript>

<script type="module">
  import '@tanrei/web/components/skeleton';
</script>
import { Skeleton } from '@tanrei/react';
import '@tanrei/web/skeleton.css';

{project ? (
  <ProjectCard project={project} />
) : (
  <Skeleton label="プロジェクトを読み込み中">
    <span className="tanrei-skeleton-placeholder" data-shape="block" style={{ blockSize: 160 }} />
    <span className="tanrei-skeleton-placeholder" style={{ inlineSize: '12.5rem' }} />
    <span className="tanrei-skeleton-placeholder" data-lines={3} />
  </Skeleton>
)}

Web では、スクリプトが動く前はスタイルシートがコンポーネントのすべてです。形、表示までの待ち時間、パルス、動きを減らす設定での静止、強制カラーモードの輪郭を描きます。要素は、スタイルシートにはできないことを加えます。支援技術に伝えるロールと名前、スケルトンの中のものをフォーカス順から外すこと、見えないあいだの一時停止です。

仕様

3 つの形: テキスト、ブロック、円

テキストのプレースホルダーは、置かれた場所のテキストスタイルに従います。見出しの中、段落の中

構成要素

パーツ 説明
1 container The region that stands in the content's place: a block as wide as the place it stands in and as tall as what it holds, with no fill, no edge, no padding and no elevation. It is what assistive technology is told about, once, and it carries no state: a skeleton takes no input.
2 placeholder A quiet filled shape in the place of one piece of the content: one or more lines of text, a block (a picture, a card, a control) or a circle (a person's picture, a round mark). It has the size the content will have and says nothing by itself.

プロパティ

プロパティ 型 既定値 説明
label string "" What is loading, in the product's language ("Loading projects"): the name assistive technology is given for the whole skeleton, once. Never drawn. Left empty, it reads the product's words; Tanrei ships none of its own. Not read while decorative is true.
decorative boolean false Whether something else already says this wait: a loading indicator beside it, a status text, a region the product itself exposes as busy and names. True keeps the whole skeleton from assistive technology, so that one wait is said once; it changes nothing that is drawn.
showDelay number null How long, in milliseconds, this skeleton waits from the moment it is inserted before it is drawn and exposed to assistive technology; 0 shows it as it is inserted. Null takes comp.skeleton.motion.show-delay, the system's answer for every skeleton. Set it to 0 where the skeleton is part of the first presentation of a page or a view, which has nothing else to show; leave it where content is fetched in a view that is already there.

アイテム

このコンポーネントが繰り返すアイテムの種類です。フィクスチャはアイテムを順に並べ、各プラットフォームはそれぞれの流儀で種類ごとに名前を付けます。

placeholder

One shape in the place of one piece of the content: lines of text, a block or a circle.

プロパティ 型 既定値 説明
shape text | block | circle "text" What the placeholder stands for. Text is one or more lines of the text style in effect where it stands. Block is a box: a picture, a card, a control, a row. Circle is a round box: a person's picture, a round mark.
lines number 1 For text: how many lines the text will take, a whole number of 1 or more. The placeholder is exactly that many line boxes high. Not read for a block or a circle.
inlineSize number null The placeholder's size along the line, in points. Null leaves it to the layout: the whole inline size of its place for text and for a block, the side of the square for a circle. A size the product's own layout gives the placeholder (a share of a row, a track of a grid) is used as it is and needs no prop.
blockSize number null The placeholder's size across the line, in points: the height of a block, and the side of a circle. Null is one line of the text style in effect, or the size the product's own layout gives. Not read for text, whose height is its lines.
仕様 Web SwiftUI Jetpack Compose Flutter React Native
アイテム span TanreiSkeletonPlaceholder TanreiSkeletonPlaceholder TanreiSkeletonPlaceholder SkeletonPlaceholder
shape · プロパティ data-shape shape shape shape shape
lines · プロパティ data-lines lines lines lines lines
inlineSize · プロパティ inline-size width width width width
blockSize · プロパティ block-size height height height height
備考 The product's own element with class="tanrei-skeleton-placeholder" — a <span>, or a <div> where it is a block's child — empty, so that it has nothing to expose. data-shape is text (the default, and what a missing or unknown value draws), block or circle; data-lines the number of lines. inlineSize and blockSize are the CSS properties inline-size and block-size on the element (in its style, or by a rule of the product's): a number of the spec is that many CSS pixels, and any other length, a percentage or an aspect-ratio the product writes there is a size its layout gives (SKL-4). The text style is the inherited font, so the placeholder goes where the text will be — inside the heading, the paragraph or the cell — and is 1lh high a line. TanreiSkeletonPlaceholder(.text, lines: 3), TanreiSkeletonPlaceholder(.circle, height: 40); a .frame or an .aspectRatio the product applies is a size its layout gives. The text style is the font of the environment (.font(...) on an ancestor), measured with the reader's text size. TanreiSkeletonPlaceholder(shape, lines, width, height, modifier) inside the skeleton's scope; a size from modifier is a size the layout gives. The text style is LocalTextStyle, measured with the font scale. TanreiSkeletonPlaceholder.text(lines: 3), .block(height: 160), .circle(size: 40); inside an Expanded, a SizedBox or an AspectRatio it takes that size. The text style is DefaultTextStyle, with MediaQuery.textScalerOf. <SkeletonPlaceholder shape="text" lines={3} />; style gives a size the layout gives (flex, a percentage, aspectRatio). React Native has no inherited text style, so the product passes one of Tanrei's text styles as textStyle, and the body style is used where it gives none (SKL-3).

状態

振る舞い

EARS 記法による要件です。各プラットフォームの実装は、この ID に対応するテストで検証されます。要件の原文は仕様ファイルのものをそのまま掲載しています。

ID 要件
SKL-1 THE skeleton SHALL lay itself out as a block that takes the whole inline size of the place it stands in and is as tall as what it holds: it SHALL lay out the placeholders that stand directly in it in one column, in the order given, each at its own size and at the start edge, comp.skeleton.container.gap apart, and SHALL leave placeholders that stand inside a layout of the product's own (a row, a grid, a card's own structure) to that layout, changing neither their place nor their size. THE skeleton SHALL draw no fill, no edge and no elevation, SHALL bring no padding and no outer space of its own, SHALL set no height of its own and SHALL NOT scroll or clip.
SKL-2 THE skeleton SHALL draw each placeholder as one box, or for text one box a line, filled with comp.skeleton.placeholder.color, with the corners of its shape (comp.skeleton.placeholder.shape.text for each line of text, comp.skeleton.placeholder.shape.block for a block, comp.skeleton.placeholder.shape.circle for a circle), never larger than half the box's smaller side, and with no edge, no elevation, no shadow and nothing inside it. THE skeleton SHALL draw nothing else: no glyph, no mark, no text, and never its label.
SKL-3 WHILE a placeholder's shape is text THE skeleton SHALL size it by the text style in effect where it stands — the style text placed there would be drawn in, and sys.typescale.body where the platform has no such style and the product gave none: the placeholder SHALL be exactly as tall as lines line boxes of that style, and in each line box THE skeleton SHALL draw one bar as tall as the style's font size, centred in the line box. Each bar SHALL be as long as the placeholder's inline size (inlineSize, else the whole inline size of its place), except that WHILE lines is 2 or more the last bar SHALL be comp.skeleton.placeholder.last-line-inline-size of that size, from the start edge. The placeholder SHALL NOT read blockSize.
SKL-4 WHILE a placeholder's shape is block THE skeleton SHALL draw one box whose inline size is inlineSize, else the size the product's layout gives it, else the whole inline size of its place, and whose block size is blockSize, else the size the product's layout gives it (a stretched cell, a ratio the product set), else one line box of the text style in effect where it stands (SKL-3). The block SHALL NOT read lines.
SKL-5 WHILE a placeholder's shape is circle THE skeleton SHALL draw one square box whose side is blockSize, else inlineSize, else the size the product's layout gives it, else one line box of the text style in effect where it stands (SKL-3), and SHALL draw it round. WHERE the place is narrower than the side THE skeleton SHALL make the square as large as the place allows and keep it square: a circle is never drawn as an oval. The circle SHALL NOT read lines.
SKL-6 THE skeleton SHALL keep the box of every placeholder, and so its own, unchanged from the moment it is inserted to the moment it is removed — while it waits to be shown (SKL-7), while it is shown and while it stirs (SKL-12) — and SHALL NOT guess what is coming: it draws the placeholders the product gave, in the sizes the product gave, and nothing of the content. That the content takes the box its placeholders held is the product's to arrange, by placing each placeholder where its content will be and as that content will be placed.
SKL-7 WHEN the skeleton is inserted into a view THE skeleton SHALL wait for its show delay (SKL-9) before it is shown, and WHILE it waits SHALL draw nothing, SHALL run no animation, SHALL be absent from the accessibility tree (no role, no name, no busy state), and SHALL still take the box it takes when shown (SKL-1 to SKL-5), so that nothing around it moves when it appears; a show delay of 0 SHALL show it as it is inserted.
SKL-8 WHEN its show delay ends THE skeleton SHALL be shown: it SHALL draw its placeholders at once, at their resting opacity and without an entrance of their own, expose itself as SKL-15 says without announcing itself (SKL-16), and start the animation of SKL-12 at the beginning of a pulse.
SKL-9 THE skeleton SHALL take its show delay from showDelay, a number of milliseconds used as given (the motion dial does not scale it). WHILE showDelay is null THE skeleton SHALL use comp.skeleton.motion.show-delay in its place; IF showDelay is negative or not a finite number THE skeleton SHALL use the token likewise and in debug builds report the value it ignored.
SKL-10 THE skeleton SHALL treat its show delay as a waiting time, not motion: WHERE motion is reduced it SHALL keep it, on every platform, including one whose reduced-motion setting removes every animation; and it SHALL keep counting it while the skeleton cannot be seen (scrolled out of view, in a hidden view), because the delay measures the wait, not what is seen.
SKL-11 WHEN the skeleton is removed before its show delay ends THE skeleton SHALL have drawn, exposed and announced nothing, and WHEN it is removed while shown it SHALL disappear at once, without a motion of its own: how the content enters is the content's. WHEN it is inserted again THE skeleton SHALL wait its whole show delay again; hiding it without removing it (a hidden ancestor, scrolling) SHALL neither restart nor pause the delay (SKL-10). WHEN showDelay changes while the delay is running THE skeleton SHALL finish the running delay as it started and use the new value from its next insertion.
SKL-12 WHILE the skeleton is shown THE skeleton SHALL stir as comp.skeleton.motion.animation names, without end. pulse SHALL ease the opacity of every placeholder's fill from its resting value to comp.skeleton.placeholder.dim-opacity of it over comp.skeleton.motion.dim-duration with comp.skeleton.motion.dim-easing and back over the same time on the same curve, every placeholder of the skeleton in step with the others. wave SHALL put each point of each placeholder through the same fall and return, each point later than the skeleton's start edge by a share of one whole pulse equal to its distance from that edge over the skeleton's inline size, so that one dimmer band travels from the start edge to the end edge through all the placeholders as through one surface. none SHALL leave the placeholders still at their resting opacity. In every manner THE skeleton SHALL change the fill's opacity only: no box moves, grows or changes shape, and nothing brighter than the resting fill is drawn.
SKL-13 WHERE reduced motion is requested THE skeleton SHALL NOT stir, whatever comp.skeleton.motion.animation names: it SHALL draw its placeholders still, at their resting opacity. The shapes, the label and the busy state still say that the place is waiting, and the show delay is kept (SKL-10).
SKL-14 WHILE the skeleton is not visible (scrolled out of view, in a hidden view, or in an app in the background) THE skeleton SHALL NOT run its animation, and SHALL resume it when it becomes visible.
SKL-15 WHILE the skeleton is shown and decorative is false THE skeleton SHALL expose itself to assistive technology as one element: the progressbar role with label as its name, no current value, and the platform's busy or indeterminate state. THE skeleton SHALL expose nothing of what it holds: no placeholder, no count of placeholders or of lines, and nothing of a layout the product placed in it.
SKL-16 THE skeleton SHALL NOT be focusable and SHALL NOT be a stop in the focus order, SHALL NOT announce itself when it is shown, when it changes or when it is removed, and SHALL NOT move keyboard or screen-reader focus. Saying that the content has arrived, where the product wants it said, is the product's, as it is beside a loading indicator.
SKL-17 WHILE decorative is true THE skeleton SHALL keep itself and everything it holds from assistive technology — no role, no name, no busy state — and SHALL draw, wait and stir as it otherwise would. A skeleton that stands inside another skeleton SHALL behave as decorative whatever its prop says: one wait is said once.
SKL-18 THE skeleton SHALL take its name from label, and WHILE label is empty from the product's words (the key loadingLabel); it has no words of its own in any language. WHILE both are empty and decorative is false THE skeleton SHALL still be drawn and still be exposed as SKL-15 says, without a name, and in debug builds report once that a skeleton has no label, naming the prop.
SKL-19 THE skeleton SHALL NOT take pointer, touch or keyboard input and SHALL NOT change anything it draws under a pointer, a press or focus: it has no hovered, focused, pressed, selected or disabled state and draws no state layer and no focus indicator. Whatever the product places in it besides placeholders SHALL be drawn as given, SHALL take no input and no focus, and SHALL NOT be exposed to assistive technology: a skeleton holds the shape of content, never content.
SKL-20 WHERE the platform draws with a person's own forced colors (the web's forced-colors mode, and a platform's equivalent where it has one) THE skeleton SHALL draw each placeholder, and each line of a text placeholder, as the outline of its shape, sys.border.hairline wide and inside its box, in the platform's text color, with no fill, and SHALL NOT stir: a fill is not drawn in that mode, and a placeholder that vanished would leave a blank place that says nothing.
SKL-21 WHILE the text size is enlarged THE skeleton SHALL grow its text placeholders with the text style they stand in, and every placeholder whose size is a line box with them, so that the skeleton still holds the box the enlarged content will take; it SHALL keep a size given as inlineSize or blockSize as given, SHALL never draw a placeholder wider than its place, SHALL keep its gap, and SHALL need no horizontal scrolling at a width of 320 points with text at 200 %.
SKL-22 WHERE the layout direction is right-to-left THE skeleton SHALL place its placeholders at the right edge, begin the shortened last bar of a text placeholder (SKL-3) and every placeholder narrower than its place at the right, and run a wave from the right edge to the left; a block and a circle are otherwise drawn as they are in a left-to-right layout.
SKL-23 THE skeleton SHALL take its layout direction from where it is used — the direction of the nearest ancestor that sets one, and the platform's own where none does — and SHALL have no direction setting of its own; WHEN that direction changes while the skeleton is shown THE skeleton SHALL draw itself for the new direction at once, without a motion of its own, keeping its place in its animation.
SKL-24 WHEN label, decorative or a placeholder's shape, lines, inlineSize or blockSize changes, or a placeholder is added or removed, while the skeleton waits or is shown THE skeleton SHALL draw and expose the new value at once, without a motion, without an announcement, and without restarting its show delay or its animation. THE skeleton SHALL neither show nor hide itself and SHALL NOT know when the content arrives: the product puts the skeleton in the content's place when the wait starts and takes it away when the wait ends, whatever the wait ends in.
SKL-25 WHEN a placeholder's shape names a value this spec does not have THE skeleton SHALL draw it as text; IF its lines is less than 1 or not a finite number THE skeleton SHALL use 1, and SHALL use the whole number below a lines that is not whole; IF its inlineSize or blockSize is negative or not a finite number THE skeleton SHALL treat it as null; WHEN comp.skeleton.motion.animation names a manner the skeleton does not know THE skeleton SHALL pulse. In each case it SHALL report the value it ignored in debug builds.
SKL-26 IF the skeleton holds no placeholder THE skeleton SHALL draw nothing, take no space and expose nothing, and in debug builds report that a skeleton holds no placeholder: a busy region with no shape in it is a blank place, and a wait whose shape is not known is a loading indicator's.

プラットフォーム別の名前対応

パリティは API が同一であることを意味しません。同じ契約を、各プラットフォームの流儀で表現します。

仕様 Web SwiftUI Jetpack Compose Flutter React Native
コンポーネント tanrei-skeleton TanreiSkeleton TanreiSkeleton TanreiSkeleton Skeleton
label · プロパティ label label label label label
decorative · プロパティ decorative isDecorative decorative decorative decorative
showDelay · プロパティ showDelay showDelay showDelay showDelay showDelay
備考 Form: the light-DOM form a component around the product's own elements takes. <tanrei-skeleton label="Loading projects"> is a custom element with **no shadow root** around the product's own layout and its placeholders, and one stylesheet, @tanrei/web/skeleton.css, draws everything — the column and its gap, the placeholders, the show delay, the animation, reduced motion and forced colors — with selectors that start at tanrei-skeleton and at the placeholder's class, wrapped in :where(). **This is the point of the form: a skeleton is what a page shows before its script has run**, so it must be whole before the element is defined and where script never runs; a shadow root would draw nothing until the module arrived, which is the wait the skeleton is for. The show delay without script is an animation with a delay (animation: <appear> 0s linear var(--tanrei-comp-skeleton-motion-show-delay) both from visibility: hidden), so the box is held and nothing is drawn or exposed until it ends (SKL-7); the show-delay attribute sets a custom property the stylesheet reads, and attribute selectors cover show-delay="0" before definition. pulse is an opacity animation of the placeholders with animation-direction: alternate; wave is one mask or background gradient sized to the host and positioned per placeholder (background-attachment does not serve here; the element measures the placeholders' offsets and writes them as custom properties once defined, and before definition a wave is drawn as a pulse — say so in the docs); prefers-reduced-motion: reduce removes the animation and keeps the delay (SKL-10, SKL-13); forced-colors: active replaces the fill with an outline in CanvasText (SKL-20). The choice of manner reaches the stylesheet as the custom property that carries its name, read by a style query or, where those are missing, by the element. A text placeholder of several lines is one element calc(var(--lines) * 1lh) high whose bars are drawn as backgrounds; whether the bars can have eased corners and a shortened last bar from CSS alone in three engines, or need the element to write one child a line once defined, is the build's to find out — the box must be the same either way. What the element adds once defined, and only then: role="progressbar", aria-busy="true" and aria-label on the host when the delay ends, unless decorative or nested (then aria-hidden="true"); inert on the host, so that nothing inside takes focus or is read (SKL-19); pausing the animation while the host is not visible (SKL-14); the debug reports. A page that must be right before its script runs, or without it, writes role, aria-busy and aria-label on the element itself, and the element leaves what it finds; the placeholders are empty elements and expose nothing either way. The React wrapper renders the element around its children. Where script never runs the skeleton never ends unless the server replaces it: a product whose content arrives only by script gives the content, or a sentence, in <noscript>. Proposed spelling, for the platform's agent to settle. TanreiSkeleton("Loading projects", isDecorative:, showDelay:) { … }, a view builder that holds placeholders directly (stacked, SKL-1) or inside the product's own stacks and grids. Not the platform's own redaction of a view, whose look is the platform's and which needs the real view to exist: the same idea drawn from Tanrei's tokens. Exposed as one accessibility element with the label, the progress trait's indeterminate form (updatesFrequently is not set) and its children ignored; accessibilityHidden while decorative or waiting. accessibilityReduceMotion stops the animation; the show delay is a task, not an animation. Not built on this platform yet: the stage color a fixture's surface names, which the text-lines-on-container-highest fixture draws on. TanreiSkeleton(label, decorative, showDelay) { … }, a column scope. clearAndSetSemantics { contentDescription = label; progressBarRangeInfo = ProgressBarRangeInfo.Indeterminate } once shown, and clearAndSetSemantics { } with invisibleToUser() while decorative or waiting. The animation reads the system's animator duration scale: where it is 0 the placeholders are still, and the show delay is a coroutine delay that the scale does not shorten (SKL-10). Not built on this platform yet: the stage color a fixture's surface names, which the text-lines-on-container-highest fixture draws on. TanreiSkeleton(label: …, decorative:, showDelay:, children: […]), or child: for a layout of the product's own. Semantics(label: label, excludeSemantics: true) with the progress indicator's indeterminate semantics once shown; ExcludeSemantics and IgnorePointer around everything it holds. MediaQuery.disableAnimationsOf stops the animation; the show delay is a Timer. Not built on this platform yet: the stage color a fixture's surface names, which the text-lines-on-container-highest fixture draws on. <Skeleton label="Loading projects">…</Skeleton>. accessible, accessibilityRole="progressbar", accessibilityLabel and accessibilityState={{ busy: true }} once shown; importantForAccessibility="no-hide-descendants" and accessibilityElementsHidden while decorative or waiting; pointerEvents="none". AccessibilityInfo.isReduceMotionEnabled stops the animation. Follows the app's direction where an ancestor's is not known. Not built on this platform yet: the stage color a fixture's surface names, which the text-lines-on-container-highest fixture draws on.

トークン

トークン 参照先 解決値(ライト)
--tanrei-comp-skeleton-container-gap sys.space.space100 8px
--tanrei-comp-skeleton-motion-animation — 0
--tanrei-comp-skeleton-motion-dim-duration sys.motion.duration.extra-long4 1000ms
--tanrei-comp-skeleton-motion-dim-easing sys.motion.easing.standard cubic-bezier(0.2, 0, 0, 1)
--tanrei-comp-skeleton-motion-show-delay sys.motion.role.show-delay 300ms
--tanrei-comp-skeleton-placeholder-color sys.color.surface-variant #eeeeee
--tanrei-comp-skeleton-placeholder-dim-opacity — 0.5
--tanrei-comp-skeleton-placeholder-last-line-inline-size — 0.6
--tanrei-comp-skeleton-placeholder-shape-block sys.shape.corner.medium 8px
--tanrei-comp-skeleton-placeholder-shape-circle sys.shape.corner.full 9999px
--tanrei-comp-skeleton-placeholder-shape-text sys.shape.corner.extra-small 3px

フィクスチャ

基準となる描画パターンです。全プラットフォームが同じ ID でスナップショットを撮り、結果を比較できるようにしています。

生成元 specs/skeleton.spec.json, tokens/comp/skeleton.tokens.json, design/tokens/

ガイドライン

ボックスを確保する

スケルトンの要点は、何も動かないことです。スケルトンのボックスも、各プレースホルダーのボックスも、挿入されてから取り除かれるまで変わりません。表示を待つあいだも、現れるときも、動いているあいだも同じです。

スケルトンは何も推測しません。渡されたプレースホルダーを、渡された大きさで描きます。プレースホルダーが確保したボックスにコンテンツが収まるようにするのは、利用者側の役目です。各プレースホルダーを、そのコンテンツが入る場所に、コンテンツと同じ置き方で置きます。

  • テキストのプレースホルダーは、テキストが入る見出し、段落、セルの中に置きます。そのテキストスタイルに従い、その行数ぶんの高さにちょうどなります。読む人が文字を拡大しても同じです。
  • テキストが何行になるかは data-lines で渡します。コンテンツの形がそろっているほど、当たります。スケルトンが 1 行足りなければページは 1 行ぶん動きますが、空白のままにしておくよりは小さな動きです。
  • ブロックには、画像やコントロールの大きさを渡します。数値、比率、またはレイアウトが与える大きさです。円には、一辺の数値を 1 つ渡します。

「読み込む」を押すと、スケルトンが 2.5 秒のあいだ場所を確保し、コンテンツがそのボックスに入ります

読み込む

3 層のトークン

リファレンストークンは生の値を持ちます。システムトークンは値の用途に名前を付けます。コンポーネントトークンは 1 つの部品をそれに結び付けます。

この行は記事の下にあり、動きません。

形

data-shape 何の代わりか 大きさ
text(既定) 1 行以上のテキスト。 置かれた場所のテキストスタイルで、data-lines 行ぶんの高さ。各行は、フォントサイズと同じ高さのバーです。複数行のときは、最後の行が短くなります。段落が端で終わることはまれだからです。inline-size でバーの長さを決めます。
block 画像、カード、コントロール、行。 inline-size と block-size、またはレイアウトが与える大きさ(引き伸ばされたセル、aspect-ratio)。大きさを渡さなければ、幅は置かれた場所いっぱい、高さは 1 行ぶんです。
circle 人物の画像、丸い印。 一辺を 1 つ。block-size か inline-size で渡します。渡さなければ 1 行ぶんの高さです。

スケルトンの直接の子であるプレースホルダーは、8 の間隔で縦に並びます。それ以外(2 行の横に円を置く、カードをグリッドに並べるなど)は、スケルトンの中に置く利用者側のレイアウトです。出来合いのカードやリストのスケルトンはありません。画面は利用者のもので、それを知っているのは利用者だけです。

利用者側の行の中では、テキストのプレースホルダーやブロックに、その行から幅を与える必要があります。コンテンツと同じです。flex: 1、グリッドのトラック、または inline-size を使います。

1 つの待ち時間に、1 つの知らせ

1 つの領域のプレースホルダーは、すべて 1 つのスケルトンに入れ、label を 1 つ付けます。スケルトンの中のスケルトンは何も伝えません。

同じ待ち時間をすでにほかのものが伝えているとき(ステータス行のローディングインジケーター、ステータスの文言、利用者側でビジー状態にして名前を付けた領域など)は、decorative を設定します。スケルトンは同じように描かれ、支援技術からは隠されるので、同じ待ち時間が 2 回読まれません。

ステータス行がすでに待ち時間を伝えているので、その下のプレースホルダーは装飾です

検索中…

表示までに待つ

スケルトンは、挿入されてから 300 ms 待ってから描かれ、支援技術に伝わります。そのあいだもボックスは確保されています。それより早く終わる待ち時間では、はじめから正しい大きさだった空白だけが見えます。

  • 待ち時間が始まったらスケルトンを挿入し、コンテンツが届いたら取り除きます。待ちは挿入から数えます。スケルトンを隠しても、数え直しにはなりません。
  • スケルトンがページやビューの最初の表示の一部で、ほかに見せるものがないときは、show-delay="0" を設定します。
  • この待ちは時間であって、モーションではありません。動きを減らす設定でも保たれます。

必ず何かで終わる

スケルトンは、コンテンツに置き換えます。何も来なかったときは空の状態に、読み込みに失敗したときはエラーのステータスのコールアウトに置き換えます。スケルトンが自分で時間切れになることはありません。いつ諦めるかを知っているのは利用者側です。

スケルトンは、コンテンツが届いたことを読み上げません。届いたことを伝えるべき場面(検索に応えた結果、求められたリストなど)では、利用者側のライブリージョンか、求めた側のコンポーネントのステータスの文言で伝えます。コンテンツとともに開くだけのビューに、読み上げは要りません。

モーション

既定では、プレースホルダーはパルスします。塗りが半分まで薄くなって戻る動きを、すべてのプレースホルダーがそろって、2 秒に 1 回くり返します。変わるのは不透明度だけです。何も動かず、静止時の塗りより明るいものは描かれません。

デザインシステムは、トークン comp.skeleton.motion.animation で、すべてのスケルトンの動き方を選べます。pulse、wave(同じ薄まりが 1 本の帯として、スケルトン全体を先頭側の端から末尾側の端へ進みます)、none のいずれかです。インスタンスごとの設定はありません。

動きを減らす設定では、プレースホルダーは静止します。その場所が待っていることは、形、ラベル、ビジー状態がすでに伝えています。スケルトンが見えないあいだ、アニメーションは動きません。

Web で、スクリプトの前に

スケルトンは、ページのスクリプトが動く前に見せるものです。そのため @tanrei/web/skeleton.css は、要素なしでスケルトンの全体を描きます。要素が必要なものがいくつかあり、要素が定義されるまで、またはスクリプトが動かない場所では、次のように異なります。

  • ロールと名前。スクリプトの前から正しくなければならないページは、要素に role="progressbar"、aria-busy="true"、aria-label を自分で書きます。要素は、書かれているものをそのままにします。
  • 0 以外の show-delay。スタイルシートは、デザインシステムの待ち時間だけ待ちます。
  • 1 つのテキストのプレースホルダーでの 24 行を超える行数。
  • wave の動き方。パルスとして描かれます。
  • 表示されていないビューの中のスケルトンは、ビューが表示されたときに待ちを始めます。

スクリプトが動かない場所では、サーバーが置き換えないかぎり、スケルトンは終わりません。スクリプトでしか届かないコンテンツには、スケルトンの横に <noscript> が必要です。

推奨

  • プレースホルダーには、これから来るものの形と大きさを与えます。
  • テキストのプレースホルダーは、テキストが入る要素の中に置きます。
  • 1 つの待ち時間には、ラベルを 1 つ付けたスケルトンを 1 つ使います。
  • 待ち時間がどう終わっても、終わったら置き換えます。

非推奨

  • 読んだり使ったりするものを、スケルトンの中に置かないでください。中のものは入力を受けず、支援技術からも隠されます。すでに分かっている見出しや、すでに使えるボタンは、スケルトンの外に置きます。
  • コントロールや行の中の短い待ち時間、進行中の操作には使わないでください。それはローディングインジケーターの役目です。
  • すでにあるコンテンツを、スケルトンで覆わないでください。
  • 灰色の箱を手で描かないでください。

アクセシビリティ

ロール
progressbar
ラベル付け
label is the accessible name of the whole skeleton: what is loading. Once shown, the skeleton is one element — a progressbar with no value, in the platform's busy (indeterminate) state — and its placeholders are hidden; during its show delay, and while decorative is true, it is not exposed at all. It is exposed exactly as a loading indicator is, because it says the same thing: something is loading here.
最小ターゲット
見た目のサイズが小さくても、ヒット領域は最小ターゲットサイズを満たします。 minimum 32px · pointer 44px · spacing 8px · touch 48px

キーボード操作

キー 動作

備考

  • Not interactive: the skeleton takes no input and is never focusable (SKL-16, SKL-19), so there is no target of its own to size and minimumTarget holds trivially.
  • One wait, said once. Every placeholder of a region belongs to one skeleton with one label; a skeleton inside another is silent (SKL-17). Where the same wait is already said — a loading indicator in a status row, a status text, a region the product itself marks busy and names — set decorative, or the wait is read twice.
  • The skeleton does not announce that it appeared, and does not announce that the content arrived (SKL-16): it is removed with its busy state. Where arrival should be heard (results in answer to a search, a list that was asked for), the product says so through its own live region or the status text of the component that asked; a view that simply opens with its content needs no announcement.
  • A wait shorter than the show delay shows and exposes nothing (SKL-7): no shapes that flash for a sighted person, and no progressbar that appears and vanishes for a screen-reader user.
  • Nothing a person should read or use goes inside a skeleton: what it holds is kept from input and from assistive technology (SKL-19). A heading that is already known, a button that already works, stand outside it.
  • The fill is decoration, held to no contrast ratio: comp.skeleton.placeholder.color is a low-emphasis fill, and in the default system it has the tone of the highest container step, so a placeholder on a surface of that tone is hard to see. The wait is never said by the fill alone: the label says it to assistive technology, and forced colors draw every placeholder as an outline (SKL-20).
  • Motion: a pulse dims the fill by half and returns in 2 seconds in the default system, far below the three-per-second flash threshold; nothing moves and nothing brightens. Reduced motion stops it entirely (SKL-13): a large region that pulses is more than a small mark that does, and the shapes already say that the place is waiting.
  • A skeleton that never ends tells a lie. The product replaces it with the content, with an empty state where nothing came, or with a callout of the error status where loading failed, and sets its own limit on the wait; a task that takes many seconds, or whose progress can be measured, is a progress indicator's.
  • Text size, text spacing and reflow: SKL-21. A text placeholder follows the text style it stands in, so it grows with the reader's text size; a size given in points does not.

生成元 specs/skeleton.spec.json, tokens/sys/measure.tokens.json

コンポーネントが担うこと

  • 表示されたスケルトンは、支援技術にとって 1 つの要素です。値のないプログレスバーで、ビジー状態にあり、label が名前になります。ローディングインジケーターと同じ伝え方です。伝える内容が同じだからです。
  • プレースホルダー、その数、行数は伝わりません。スケルトンの中に置いたレイアウトも伝わりません。
  • 表示を待つあいだは、まったく伝わりません。待ちより短い待ち時間で、現れてすぐ消えるプログレスバーが伝わることはありません。
  • decorative を設定したとき、またはほかのスケルトンの中にあるときは、支援技術から隠されます。
  • フォーカスを受けず、フォーカス順にも入りません。中のものも同じです。自分を読み上げず、フォーカスも動かしません。
  • 動きを減らす設定では静止します。待ちは保たれます。
  • 強制カラーモードでは、各プレースホルダーとテキストの各行を、その形の輪郭として文字色で描きます。このモードでは塗りが描かれないので、プレースホルダーが消えると空白しか残らないからです。
  • 文字を拡大すると、テキストのプレースホルダーはテキストスタイルとともに大きくなるので、拡大したコンテンツのボックスを確保し続けます。数値で渡した大きさは変わりません。置かれた場所より幅が広くなるものはなく、幅 320 で文字を 200% にしても横スクロールは生じません。
  • 右から左(RTL)のレイアウトでは、プレースホルダーは右に置かれ、短い最後の行は右から始まり、ウェーブは右から左へ進みます。
  • 未知の形、使えない行数や大きさ、プレースホルダーのないスケルトン、ラベルのないスケルトンは、デバッグビルドで報告します。

利用者側で行うこと

  • 何を読み込んでいるかを伝える label を、ページの言語で渡します(「プロジェクトを読み込み中」)。または setWords({ loadingLabel: … }) で、すべてのスケルトンの文言を一度に渡します。Tanrei は言葉を同梱しません。
  • 同じ待ち時間をほかのものが伝えているときは、decorative を設定します。
  • コンテンツが届いたことが知らせになる場面では、それを伝えます。
  • スケルトンをいつまでも残さないでください。コンテンツ、空の状態、エラーのいずれかに置き換えます。
  • 塗りは装飾で、コントラスト比の基準は課されません。最も高いコンテナのトーンの面の上では見えにくくなります。その場所が待っていることを、塗りだけで伝えないでください。そうした面の上に置くときは、レイヤーで comp.skeleton.placeholder.color をより強いロールに付け替えます。