Skip to main content
Nexus Design System
GitHub

Spinner

Spinner shows that something is loading. Use it for page loads, "loading more" rows and Suspense fallbacks. For a loading Button, use its loading prop.

'use client';

import { Spinner } from '@/components/nxds/components/spinner/spinner';

export default function SpinnerDefault() {
  return <Spinner />;
}

Installation

npx nxds-cli@latest add spinner

Run npx nxds-cli@latest init first if nxds isn’t set up in your app yet. See Install.

Usage

import { Spinner } from '@/components/nxds/components/spinner/spinner';

Examples

Sizes

Size the spinner with nx:size-* and tint it with nx:text-*.

'use client';

import { Spinner } from '@/components/nxds/components/spinner/spinner';

export default function SpinnerSizes() {
  return (
    <div className="nx:flex nx:items-center nx:gap-4 nx:text-foreground">
      <Spinner className="nx:size-4" />
      <Spinner className="nx:size-6" />
      <Spinner className="nx:size-8 nx:text-primary-subtle-foreground" />
    </div>
  );
}

With text

Place the spinner beside text that says what is loading.

Loading results…
'use client';

import { Spinner } from '@/components/nxds/components/spinner/spinner';

export default function SpinnerWithText() {
  return (
    <div className="nx:flex nx:items-center nx:gap-2 nx:typography-body-default nx:text-muted-foreground">
      <Spinner className="nx:size-4" />
      Loading results…
    </div>
  );
}

Props

Spinner

Spinner adds no props of its own; it accepts the props of the element or primitive it wraps.

Spinner shows that something is loading. For a loading Button, use its loading prop.

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

<template>
  <Spinner />
</template>

Installation

  1. Install the following dependencies:

    npm install @phosphor-icons/vue@^2.2.1
    
  2. Copy and paste the following code into your project.

    components/spinner/Spinner.vue
    <script setup lang="ts">
    import type { HTMLAttributes } from 'vue';
    
    import { IconLoader2 } from '../../lib/icons';
    import { cn } from '../../lib/utils';
    
    export interface SpinnerProps {
      class?: HTMLAttributes['class'];
    }
    
    const props = defineProps<SpinnerProps>();
    </script>
    
    <template>
      <IconLoader2
        role="status"
        aria-label="Loading"
        data-slot="spinner"
        :class="cn('nx:size-4 nx:animate-spin', props.class)"
      />
    </template>
    
    components/spinner/index.ts
    export { default as Spinner, type SpinnerProps } from './Spinner.vue';
    
    lib/icons.ts
    /**
     * Internal Icons
     *
     * This barrel file re-exports icons used internally by the design system.
     * These are hardcoded icons for component states (loading, validation, navigation).
     *
     * User-facing icons should be passed via props by consumers.
     *
     * This set draws Phosphor. `nxds init --icons` installs the same names drawn
     * by another library.
     */
    import { PhCircle } from '@phosphor-icons/vue';
    import { type FunctionalComponent, h } from 'vue';
    
    // Loading states
    export { PhCircleNotch as IconLoader2 } from '@phosphor-icons/vue';
    
    // Navigation
    export {
      PhArrowDown as IconArrowDown,
      PhArrowLeft as IconArrowLeft,
      PhArrowRight as IconArrowRight,
      PhArrowUp as IconArrowUp,
      PhCaretDown as IconChevronDown,
      PhCaretLeft as IconChevronLeft,
      PhCaretRight as IconChevronRight,
      PhCaretUp as IconChevronUp,
      PhDotsThree as IconDots,
      PhSidebarSimple as IconLayoutSidebar,
      PhCaretUpDown as IconSelector,
    } from '@phosphor-icons/vue';
    
    // Actions
    export {
      PhCheck as IconCheck,
      PhCopy as IconCopy,
      PhMinus as IconMinus,
      PhPencilSimple as IconPencil,
      PhPlus as IconPlus,
      PhX as IconX,
    } from '@phosphor-icons/vue';
    
    export const IconCircleFilled: FunctionalComponent = (_props, { attrs }) =>
      h(PhCircle, { weight: 'fill', ...attrs });
    
    // Filter field types
    export {
      PhCalendarBlank as IconCalendar,
      PhHash as IconHash,
      PhTextAa as IconLetterCase,
      PhListBullets as IconList,
    } from '@phosphor-icons/vue';
    
    // Search / input
    export { PhMagnifyingGlass as IconSearch } from '@phosphor-icons/vue';
    
    // Alerts/Status
    export {
      PhWarningCircle as IconAlertCircle,
      PhWarning as IconAlertTriangle,
      PhCheckCircle as IconCircleCheck,
      PhInfo as IconInfoCircle,
    } from '@phosphor-icons/vue';
    
    // Layout
    export {
      PhDotsSixVertical as IconGripVertical,
      PhPalette as IconPalette,
    } from '@phosphor-icons/vue';
    
  3. Keep each file at the path shown above it.

    Paths are relative to packages/vue/src/. Keep the same folders under your @/ root, and the imports between files work without edits.

Usage

import { Spinner } from '@/components/spinner';

Examples

Sizes

Size the spinner with nx:size-* and tint it with nx:text-*.

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

<template>
  <div class="nx:flex nx:items-center nx:gap-4 nx:text-foreground">
    <Spinner class="nx:size-4" />
    <Spinner class="nx:size-6" />
    <Spinner class="nx:size-8 nx:text-primary-subtle-foreground" />
  </div>
</template>

With text

Place the spinner beside text that says what is loading.

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

<template>
  <div
    class="nx:flex nx:items-center nx:gap-2 nx:typography-body-default nx:text-muted-foreground"
  >
    <Spinner class="nx:size-4" />
    Loading results…
  </div>
</template>

Props

Spinner

Spinner adds no props of its own; it accepts the props of the element or primitive it wraps.