6.9k

Spinner

Previous Next

An indicator that can be used to show a loading state.

Processing payment...
$100.00
<script lang="ts">
  import { Spinner } from "$lib/components/ui/spinner/index.js";
  import * as Item from "$lib/components/ui/item/index.js";
</script>
 
<div class="flex w-full max-w-xs flex-col gap-4 [--radius:1rem]">
  <Item.Root variant="muted">
    <Item.Media>
      <Spinner />
    </Item.Media>
    <Item.Content>
      <Item.Title class="line-clamp-1">Processing payment...</Item.Title>
    </Item.Content>
    <Item.Content class="flex-none justify-end">
      <span class="text-sm tabular-nums">$100.00</span>
    </Item.Content>
  </Item.Root>
</div>

Installation

pnpm dlx shadcn-svelte@latest add spinner

Usage

<script lang="ts">
  import { Spinner } from "$lib/components/ui/spinner/index.js";
</script>
<Spinner />

Customization

You can replace the default spinner icon with any other icon by editing the Spinner component.

<script lang="ts">
  import { cn } from "$lib/utils.js";
  import LoaderIcon from "@lucide/svelte/icons/loader";
  import type { ComponentProps } from "svelte";
 
  type Props = ComponentProps<typeof LoaderIcon>;
 
  let { class: className, ...restProps }: Props = $props();
</script>
 
<LoaderIcon
  role="status"
  aria-label="Loading"
  class={cn("size-4 animate-spin", className)}
  {...restProps}
/>

Examples

Size

Use the size-* utility class to change the size of the spinner.

<script lang="ts">
  import { Spinner } from "$lib/components/ui/spinner/index.js";
</script>
 
<div class="flex items-center gap-6">
  <Spinner class="size-3" />
  <Spinner class="size-4" />
  <Spinner class="size-6" />
  <Spinner class="size-8" />
</div>

Color

Use the text-* utility class to change the color of the spinner.

<script lang="ts">
  import { Spinner } from "$lib/components/ui/spinner/index.js";
</script>
 
<div class="flex items-center gap-6">
  <Spinner class="size-6 text-red-500" />
  <Spinner class="size-6 text-green-500" />
  <Spinner class="size-6 text-blue-500" />
  <Spinner class="size-6 text-yellow-500" />
  <Spinner class="size-6 text-purple-500" />
</div>

Button

Add a spinner to a button to indicate a loading state. The <Button /> will handle the spacing between the spinner and the text.

<script lang="ts">
  import { Button } from "$lib/components/ui/button/index.js";
  import { Spinner } from "$lib/components/ui/spinner/index.js";
</script>
 
<div class="flex flex-col items-center gap-4">
  <Button disabled size="sm">
    <Spinner />
    Loading...
  </Button>
  <Button variant="outline" disabled size="sm">
    <Spinner />
    Please wait
  </Button>
  <Button variant="secondary" disabled size="sm">
    <Spinner />
    Processing
  </Button>
</div>

Badge

You can also use a spinner inside a badge.

Syncing Updating Loading
<script lang="ts">
  import { Badge } from "$lib/components/ui/badge/index.js";
  import { Spinner } from "$lib/components/ui/spinner/index.js";
</script>
 
<div class="flex items-center gap-2">
  <Badge>
    <Spinner />
    Syncing
  </Badge>
  <Badge variant="secondary">
    <Spinner />
    Updating
  </Badge>
  <Badge variant="outline">
    <Spinner />
    Loading
  </Badge>
</div>

Input Group

Input Group can have spinners inside <InputGroup.Addon>.

Validating...
<script lang="ts">
  import * as InputGroup from "$lib/components/ui/input-group/index.js";
  import { Spinner } from "$lib/components/ui/spinner/index.js";
  import ArrowUpIcon from "@lucide/svelte/icons/arrow-up";
</script>
 
<div class="flex w-full max-w-md flex-col gap-4">
  <InputGroup.Root>
    <InputGroup.Input placeholder="Send a message..." disabled />
    <InputGroup.Addon align="inline-end">
      <Spinner />
    </InputGroup.Addon>
  </InputGroup.Root>
  <InputGroup.Root>
    <InputGroup.Textarea placeholder="Send a message..." disabled />
    <InputGroup.Addon align="block-end">
      <Spinner /> Validating...
      <InputGroup.Button class="ms-auto" variant="default">
        <ArrowUpIcon />
        <span class="sr-only">Send</span>
      </InputGroup.Button>
    </InputGroup.Addon>
  </InputGroup.Root>
</div>

Empty

Processing your request
Please wait while we process your request. Do not refresh the page.
<script lang="ts">
  import * as Empty from "$lib/components/ui/empty/index.js";
  import { Spinner } from "$lib/components/ui/spinner/index.js";
  import { Button } from "$lib/components/ui/button/index.js";
</script>
 
<Empty.Root class="w-full border md:p-6">
  <Empty.Header>
    <Empty.Media variant="icon">
      <Spinner />
    </Empty.Media>
    <Empty.Title>Processing your request</Empty.Title>
    <Empty.Description>
      Please wait while we process your request. Do not refresh the page.
    </Empty.Description>
  </Empty.Header>
  <Empty.Content>
    <Button variant="outline" size="sm">Cancel</Button>
  </Empty.Content>
</Empty.Root>

Item

Use the spinner inside <Item.Media> to indicate a loading state.

Downloading...

129 MB / 1000 MB

<script lang="ts">
  import * as Item from "$lib/components/ui/item/index.js";
  import { Spinner } from "$lib/components/ui/spinner/index.js";
  import { Button } from "$lib/components/ui/button/index.js";
  import { Progress } from "$lib/components/ui/progress/index.js";
</script>
 
<div class="flex w-full max-w-md flex-col gap-4 [--radius:1rem]">
  <Item.Root variant="outline">
    <Item.Media variant="icon">
      <Spinner />
    </Item.Media>
    <Item.Content>
      <Item.Title>Downloading...</Item.Title>
      <Item.Description>129 MB / 1000 MB</Item.Description>
    </Item.Content>
    <Item.Actions class="hidden sm:flex">
      <Button variant="outline" size="sm">Cancel</Button>
    </Item.Actions>
    <Item.Footer>
      <Progress value={75} />
    </Item.Footer>
  </Item.Root>
</div>