Theme
Version
GitHub PyPI Discord
On this page

Skeleton

Use CSkeleton to hold a known layout while its data loads. Compose explicit primitives instead of encoding a page shape in a preset string.

Skeleton at a glance

Skeleton at a glance
Show code
import citry_ui
from citry import Component, citry

citry.register_library(citry_ui)


class SkeletonGlance(Component):
    template = """
      <section class="skeleton-glance" aria-label="Loading field note" aria-busy="true">
        <c-CSkeleton height="8rem" animation="wave" />
        <c-CGroup c-gap="'sm'">
          <c-CSkeleton kind="circle" width="2.75rem" />
          <c-CSkeleton kind="text" c-lines="3" />
        </c-CGroup>
      </section>
    """
    css = """
      :where(.skeleton-glance) {
        display: grid;
        max-inline-size: 24rem;
        gap: 1rem;
        padding: 1rem;
        border: 1px solid light-dark(#b8cbb9, #425947);
        border-radius: 0.9rem;
      }
    """


preview = SkeletonGlance()
preview  # noqa: B018

Choose a primitive

Rectangles hold media and panels, circles hold avatars and icons, and text lines track typography.

Compare Skeleton primitives
Show code
import citry_ui
from citry import Component, citry

citry.register_library(citry_ui)


class SkeletonPrimitives(Component):
    template = """
      <div class="skeleton-primitives" aria-label="Loading archive specimens" aria-busy="true">
        <c-CSkeleton width="10rem" height="5rem" />
        <c-CSkeleton kind="circle" width="3rem" />
        <c-CSkeleton kind="text" width="12rem" />
      </div>
    """
    css = """
      :where(.skeleton-primitives) {
        display: flex;
        flex-wrap: wrap;
        align-items: center;
        gap: 1rem;
      }
    """


preview = SkeletonPrimitives()
preview  # noqa: B018

Shape text

lines produces compact paragraph geometry. Set the final line width to make the placeholder resemble real prose.

Compose text lines
Show code
import citry_ui
from citry import Component, citry

citry.register_library(citry_ui)


class SkeletonText(Component):
    template = """
      <div class="skeleton-text" aria-label="Loading fern description" aria-busy="true">
        <c-CSkeleton kind="text" height="1.15rem" width="55%" />
        <c-CSkeleton kind="text" c-lines="4" last_line_width="38%" />
      </div>
    """
    css = """
      :where(.skeleton-text) {
        display: grid;
        max-inline-size: 30rem;
        gap: 1rem;
      }
    """


preview = SkeletonText()
preview  # noqa: B018

Compose real layouts

Build familiar patterns with CStack, CGroup, and ordinary CSS. The visible structure stays inspectable and responsive.

Compose a field-note card
Show code
import citry_ui
from citry import Component, citry

citry.register_library(citry_ui)


class SkeletonCard(Component):
    template = """
      <c-CCard c-attrs="{'aria-label': 'Loading moonfern field note', 'aria-busy': 'true'}">
        <c-fill name="media"><c-CSkeleton height="9rem" /></c-fill>
        <c-fill name="default">
          <c-CStack c-gap="'sm'">
            <c-CSkeleton kind="text" height="1.2rem" width="48%" />
            <c-CSkeleton kind="text" c-lines="3" />
          </c-CStack>
        </c-fill>
      </c-CCard>
    """


preview = SkeletonCard()
preview  # noqa: B018
Compose a specimen list
Show code
import citry_ui
from citry import Component, citry

citry.register_library(citry_ui)


class SkeletonList(Component):
    class Kwargs:
        pass

    class Slots:
        pass

    template = """
      <div class="skeleton-list" aria-label="Loading specimen index" aria-busy="true">
        <c-for each="item in items">
          <c-CGroup #c-key="item" c-gap="'sm'" c-align="'center'">
            <c-CSkeleton kind="circle" width="2.5rem" />
            <c-CSkeleton kind="text" c-lines="2" c-last_line_width="f'{45 + item * 8}%'" />
            <c-CSkeleton width="3.5rem" height="1.5rem" />
          </c-CGroup>
        </c-for>
      </div>
    """

    def template_data(
        self,
        kwargs: Kwargs,  # noqa: ARG002
        slots: Slots,  # noqa: ARG002
    ) -> dict[str, object]:
        return {"items": (0, 1, 2)}

    css = """
      :where(.skeleton-list) {
        display: grid;
        max-inline-size: 30rem;
        gap: 1rem;
      }

      :where(.skeleton-list [data-citry-ui-part="group"] > :nth-child(2)) {
        flex: 1 1 auto;
      }
    """


preview = SkeletonList()
preview  # noqa: B018

Choose motion

Pulse is the default. Wave provides stronger progress motion, while none makes a static wireframe. Reduced-motion preferences disable both animations.

Compare motion treatments
Show code
import citry_ui
from citry import Component, citry

citry.register_library(citry_ui)


class SkeletonMotion(Component):
    class Kwargs:
        pass

    class Slots:
        pass

    template = """
      <div class="skeleton-motion" aria-label="Loading archive shelves" aria-busy="true">
        <c-for each="motion in motions">
          <div><span>{{ motion }}</span><c-CSkeleton c-animation="motion" height="2.5rem" /></div>
        </c-for>
      </div>
    """

    def template_data(
        self,
        kwargs: Kwargs,  # noqa: ARG002
        slots: Slots,  # noqa: ARG002
    ) -> dict[str, object]:
        return {"motions": ("pulse", "wave", "none")}

    css = """
      :where(.skeleton-motion) {
        display: grid;
        gap: 0.75rem;
      }

      :where(.skeleton-motion > div) {
        display: grid;
        grid-template-columns: 4rem 1fr;
        align-items: center;
        gap: 0.75rem;
        font: 0.75rem ui-sans-serif, system-ui, sans-serif;
      }
    """


preview = SkeletonMotion()
preview  # noqa: B018

Customize Skeleton

Public variables control dimensions, color, radius, spacing, and timing.

Customize Skeleton with public CSS
Show code
import citry_ui
from citry import Component, citry

citry.register_library(citry_ui)


class SkeletonCustomization(Component):
    template = """
      <div class="skeleton-lichen" aria-label="Loading lichen plates" aria-busy="true">
        <c-CSkeleton height="5rem" animation="wave" />
        <c-CSkeleton kind="text" c-lines="3" />
      </div>
    """
    css = """
      :where(.skeleton-lichen) {
        --cui-skeleton-background: light-dark(#c9dfc8, #36513c);
        --cui-skeleton-highlight: light-dark(rgb(255 255 255 / 70%), rgb(190 239 200 / 28%));
        --cui-skeleton-radius: 1rem;
        display: grid;
        max-inline-size: 24rem;
        gap: 1rem;
      }
    """


preview = SkeletonCustomization()
preview  # noqa: B018

Accessibility and loading ownership

Skeletons are decorative and hidden from assistive technology. Put aria-busy="true" and a useful accessible name on the region whose content is loading. That region, not Skeleton, owns async state and announcements.

API reference

Inputs

CSkeleton server inputs

Server inputs are passed in a template through <c-CSkeleton ... /> or in Python through CSkeleton(...).

InputTypeDefaultEffect
kind"rect" | "text" | "circle" (CSkeletonKind)"rect"Selects primitive geometry.
linesint (1..100)1Renders one or more text lines; values above one require text kind.
animation"pulse" | "wave" | "none" (CSkeletonAnimation)"pulse"Selects CSS-only motion. Reduced-motion always disables it.
widthstr | NoneNoneSets the root width to one CSS length or percentage.
heightstr | NoneNoneSets the primitive or line height to one CSS length or percentage.
last_line_widthstr"70%"Sets the final line width when multiple text lines render.
class_str | Mapping[str, bool] | Sequence[CClassValue] | None (CClassValue)NoneAdds root classes and merges them with attrs.
stylestr | Mapping[str, str | int | float | bool | None] | Sequence[CStyleValue] | None (CStyleValue)NoneAdds root inline styles before direct dimension inputs.
attrsMapping[str, object] | NoneNoneAdds copied trusted root attributes without replacing decorative semantics, children, reflections, focus, or Citry runtime fields.

Slots

-

Events

-

Methods

-

CSS

CSS variables to theme the components. Set them on an ancestor or the component itself.

CSkeleton CSS variables

Apply these variables to CSkeleton or one of its ancestors.

VariableTypePurposeDefault
--cui-skeleton-widthlength-or-percentageRoot or line-group width.Kind-derived 100% or 3rem.
--cui-skeleton-heightlength-or-percentageRoot or line height.Kind-derived 6rem, 0.75em, or 3rem.
--cui-skeleton-radiuslengthPrimitive corners.Kind-derived 0.5rem, 999px, or 50%.
--cui-skeleton-backgroundcolorResting placeholder surface.Scheme-derived neutral.
--cui-skeleton-highlightcolorWave highlight.Translucent white.
--cui-skeleton-gaplengthText line gap.0.5em
--cui-skeleton-durationtimePulse or wave cycle.1.5s
--cui-skeleton-last-line-widthlength-or-percentageFinal text-line width.Input-derived 70%.

Attributes

HTML attributes defined on the components that you can refer to for CSS, inspection, and testing. Read-only.

CSkeleton attributes

AttributeElementTypeMeaning
data-kindRoot"rect" | "text" | "circle"Reflects primitive geometry.
data-animationRoot"pulse" | "wave" | "none"Reflects requested motion.
aria-hiddenRoot"true"Keeps decorative placeholder geometry out of the accessibility tree.

Selectors

Selectors for the DOM nodes in the components that you can use for CSS, inspection, and testing.

CSkeleton selectors

SelectorElementPurpose
[data-citry-ui-part="skeleton"]Root spanStable primitive and attrs destination.
[data-citry-ui-part="line"]Text line spanStable direct child in text mode.

Interfaces

Aliases and data shapes referenced above.

Input type aliases

InterfaceDefinition
CClassValuestr | Mapping[str, bool] | Sequence[CClassValue]
CStyleValuestr | Mapping[str, str | int | float | bool | None] | Sequence[CStyleValue]
CSkeletonKindLiteral["rect", "text", "circle"]
CSkeletonAnimationLiteral["pulse", "wave", "none"]

Translation keys

-