8.7k

Spinner

PreviousNext

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

<script setup lang="ts">
import { Button } from '@/components/ui/button'
import { Spinner } from '@/components/ui/spinner'
</script>

<template>
  <Button disabled>
    <Spinner />
    Processing Payment
  </Button>
</template>

Installation

pnpm dlx shadcn-vue@latest add spinner

Usage

<script setup lang="ts">
import { Spinner } from '@/components/ui/Spinner'
</script>

<template>
  <Spinner />
</template>

Customization

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

<script setup lang="ts">
import type { HTMLAttributes } from 'vue'
import { LoaderIcon } from 'lucide-vue-next'
import { cn } from '@/lib/utils'

const props = defineProps<{
  class?: HTMLAttributes['class']
}>()
</script>

<template>
  <LoaderIcon
    role="status"
    aria-label="Loading"
    :class="cn('size-4 animate-spin', props.class)"
  />
</template>

Examples

Size

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

<script setup lang="ts">
import { Spinner } from '@/components/ui/spinner'
</script>

<template>
  <div class="flex items-center gap-6">
    <Spinner class="size-3" />
    <Spinner class="size-4" />
    <Spinner class="size-6" />
    <Spinner class="size-8" />
  </div>
</template>

Color

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

<script setup lang="ts">
import { Spinner } from '@/components/ui/spinner'
</script>

<template>
  <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>
</template>

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 setup lang="ts">
import { Button } from '@/components/ui/button'
import { Spinner } from '@/components/ui/spinner'
</script>

<template>
  <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>
</template>

Badge

You can also use a spinner inside a badge.

Syncing
Updating
Processing
<script setup lang="ts">
import { Badge } from '@/components/ui/badge'
import { Spinner } from '@/components/ui/spinner'
</script>

<template>
  <div class="flex items-center gap-4 [--radius:1.2rem]">
    <Badge>
      <Spinner />
      Syncing
    </Badge>
    <Badge variant="secondary">
      <Spinner />
      Updating
    </Badge>
    <Badge variant="outline">
      <Spinner />
      Processing
    </Badge>
  </div>
</template>

Input Group

Input Group can have spinners inside <InputGroupAddon>.

Validating...
<script setup lang="ts">
import { ArrowUpIcon } from 'lucide-vue-next'
import {
  InputGroup,
  InputGroupAddon,
  InputGroupButton,
  InputGroupInput,
  InputGroupTextarea,
} from '@/components/ui/input-group'
import { Spinner } from '@/components/ui/spinner'
</script>

<template>
  <div class="flex w-full max-w-md flex-col gap-4">
    <InputGroup>
      <InputGroupInput placeholder="Send a message..." disabled />
      <InputGroupAddon align="inline-end">
        <Spinner />
      </InputGroupAddon>
    </InputGroup>
    <InputGroup>
      <InputGroupTextarea placeholder="Send a message..." disabled />
      <InputGroupAddon align="block-end">
        <Spinner /> Validating...
        <InputGroupButton class="ml-auto" variant="default">
          <ArrowUpIcon />
          <span class="sr-only">Send</span>
        </InputGroupButton>
      </InputGroupAddon>
    </InputGroup>
  </div>
</template>

Empty

You can place a spinner inside an empty state.

Processing your request

Please wait while we process your request. Do not refresh the page.

<script setup lang="ts">
import { Button } from '@/components/ui/button'
import {
  Empty,
  EmptyContent,
  EmptyDescription,
  EmptyHeader,
  EmptyMedia,
  EmptyTitle,
} from '@/components/ui/empty'
import { Spinner } from '@/components/ui/spinner'
</script>

<template>
  <Empty class="w-full">
    <EmptyHeader>
      <EmptyMedia variant="icon">
        <Spinner />
      </EmptyMedia>
      <EmptyTitle>Processing your request</EmptyTitle>
      <EmptyDescription>
        Please wait while we process your request. Do not refresh the page.
      </EmptyDescription>
    </EmptyHeader>
    <EmptyContent>
      <Button variant="outline" size="sm">
        Cancel
      </Button>
    </EmptyContent>
  </Empty>
</template>

Item

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

Downloading...

129 MB / 1000 MB

<script setup lang="ts">
import { Button } from '@/components/ui/button'
import {
  Item,
  ItemActions,
  ItemContent,
  ItemDescription,
  ItemFooter,
  ItemMedia,
  ItemTitle,
} from '@/components/ui/item'
import { Progress } from '@/components/ui/progress'
import { Spinner } from '@/components/ui/spinner'
</script>

<template>
  <div class="flex w-full max-w-md flex-col gap-4 [--radius:1rem]">
    <Item variant="outline">
      <ItemMedia variant="icon">
        <Spinner />
      </ItemMedia>
      <ItemContent>
        <ItemTitle>Downloading...</ItemTitle>
        <ItemDescription>129 MB / 1000 MB</ItemDescription>
      </ItemContent>
      <ItemActions class="hidden sm:flex">
        <Button variant="outline" size="sm">
          Cancel
        </Button>
      </ItemActions>
      <ItemFooter>
        <Progress :model-value="75" />
      </ItemFooter>
    </Item>
  </div>
</template>

API Reference

Spinner

Use the Spinner component to display a spinner.

PropTypeDefault
classstring
<template>
  <Spinner />
</template>