carousel

A carousel with motion and swipe built using Embla.

PreviousNext
1
2
3
4
5
import { ChangeDetectionStrategy, Component } from '@angular/core';

import { ZardCardImports } from '@/shared/components/card/card.imports';
import { ZardCarouselImports } from '@/shared/components/carousel/carousel.imports';

@Component({
  selector: 'z-demo-carousel-preview',
  imports: [ZardCarouselImports, ZardCardImports],
  template: `
    <div class="w-full max-w-48 sm:max-w-xs">
      <z-carousel>
        <z-carousel-content>
          @for (slide of slides; track slide) {
            <z-carousel-item>
              <div class="p-1">
                <z-card>
                  <z-card-content class="flex aspect-square items-center justify-center p-6">
                    <span class="text-4xl font-semibold">{{ slide }}</span>
                  </z-card-content>
                </z-card>
              </div>
            </z-carousel-item>
          }
        </z-carousel-content>
      </z-carousel>
    </div>
  `,
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ZardDemoCarouselPreviewComponent {
  protected slides = ['1', '2', '3', '4', '5'];
}

About

The carousel is built using the Embla Carousel library. Embla Carousel

Installation

Copy
npx zard-cli@latest add carousel

Usage

import { ZardCarouselImports } from '@/shared/components/carousel/carousel.imports';
Copy
<z-carousel>
  <z-carousel-content>
    <z-carousel-item>Slide 1</z-carousel-item>
    <z-carousel-item>Slide 2</z-carousel-item>
    <z-carousel-item>Slide 3</z-carousel-item>
  </z-carousel-content>
</z-carousel>
Copy

Examples

sizes

To set the size of the items, you can use the basis utility class on the <z-carousel-item />.
1
2
3
4
5
import { ChangeDetectionStrategy, Component } from '@angular/core';

import { ZardCardImports } from '@/shared/components/card/card.imports';
import { ZardCarouselImports } from '@/shared/components/carousel/carousel.imports';

@Component({
  selector: 'z-demo-carousel-sizes',
  imports: [ZardCarouselImports, ZardCardImports],
  template: `
    <div class="w-full max-w-48 sm:max-w-xs md:max-w-sm">
      <z-carousel [zOptions]="{ align: 'start' }">
        <z-carousel-content>
          @for (slide of slides; track slide) {
            <z-carousel-item class="basis-1/2 lg:basis-1/3">
              <div class="p-1">
                <z-card>
                  <z-card-content class="flex aspect-square items-center justify-center p-6">
                    <span class="text-3xl font-semibold">{{ slide }}</span>
                  </z-card-content>
                </z-card>
              </div>
            </z-carousel-item>
          }
        </z-carousel-content>
      </z-carousel>
    </div>
  `,
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ZardDemoCarouselSizesComponent {
  protected slides = ['1', '2', '3', '4', '5'];
}
<!-- 33% of the carousel width. -->
<z-carousel>
  <z-carousel-content>
    <z-carousel-item class="basis-1/3">...</z-carousel-item>
    <z-carousel-item class="basis-1/3">...</z-carousel-item>
    <z-carousel-item class="basis-1/3">...</z-carousel-item>
  </z-carousel-content>
</z-carousel>
Copy
<!-- 50% on small screens and 33% on larger screens. -->
<z-carousel>
  <z-carousel-content>
    <z-carousel-item class="md:basis-1/2 lg:basis-1/3">...</z-carousel-item>
    <z-carousel-item class="md:basis-1/2 lg:basis-1/3">...</z-carousel-item>
    <z-carousel-item class="md:basis-1/2 lg:basis-1/3">...</z-carousel-item>
  </z-carousel-content>
</z-carousel>
Copy

spacing

To set the spacing between the items, we use a pl-[VALUE] utility on the <z-carousel-item /> and a negative -ml-[VALUE] on the <z-carousel-content />.
1
2
3
4
5
import { ChangeDetectionStrategy, Component } from '@angular/core';

import { ZardCardImports } from '@/shared/components/card/card.imports';
import { ZardCarouselImports } from '@/shared/components/carousel/carousel.imports';

@Component({
  selector: 'z-demo-carousel-spacing',
  imports: [ZardCarouselImports, ZardCardImports],
  template: `
    <div class="w-full max-w-48 sm:max-w-xs md:max-w-sm">
      <z-carousel>
        <z-carousel-content class="-ml-1">
          @for (slide of slides; track slide) {
            <z-carousel-item class="basis-1/2 pl-1 lg:basis-1/3">
              <div class="p-1">
                <z-card>
                  <z-card-content class="flex aspect-square items-center justify-center p-6">
                    <span class="text-2xl font-semibold">{{ slide }}</span>
                  </z-card-content>
                </z-card>
              </div>
            </z-carousel-item>
          }
        </z-carousel-content>
      </z-carousel>
    </div>
  `,
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ZardDemoCarouselSpacingComponent {
  protected slides = ['1', '2', '3', '4', '5'];
}
<z-carousel>
  <z-carousel-content class="-ml-4">
    <z-carousel-item class="pl-4">...</z-carousel-item>
    <z-carousel-item class="pl-4">...</z-carousel-item>
    <z-carousel-item class="pl-4">...</z-carousel-item>
  </z-carousel-content>
</z-carousel>
Copy
<z-carousel>
  <z-carousel-content class="-ml-2 md:-ml-4">
    <z-carousel-item class="pl-2 md:pl-4">...</z-carousel-item>
    <z-carousel-item class="pl-2 md:pl-4">...</z-carousel-item>
    <z-carousel-item class="pl-2 md:pl-4">...</z-carousel-item>
  </z-carousel-content>
</z-carousel>
Copy

orientation

Use the zOrientation input to set the orientation of the carousel.
1
2
3
4
5
import { ChangeDetectionStrategy, Component } from '@angular/core';

import { ZardCardImports } from '@/shared/components/card/card.imports';
import { ZardCarouselImports } from '@/shared/components/carousel/carousel.imports';

@Component({
  selector: 'z-demo-carousel-orientation',
  imports: [ZardCarouselImports, ZardCardImports],
  template: `
    <div class="w-full min-w-xs">
      <z-carousel [zOptions]="{ align: 'start' }" zOrientation="vertical">
        <z-carousel-content class="-mt-1 h-[270px]">
          @for (slide of slides; track slide) {
            <z-carousel-item class="basis-1/2 pt-1">
              <div class="p-1">
                <z-card>
                  <z-card-content class="flex items-center justify-center p-6">
                    <span class="text-3xl font-semibold">{{ slide }}</span>
                  </z-card-content>
                </z-card>
              </div>
            </z-carousel-item>
          }
        </z-carousel-content>
      </z-carousel>
    </div>
  `,
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ZardDemoCarouselOrientationComponent {
  protected slides = ['1', '2', '3', '4', '5'];
}
<z-carousel zOrientation="vertical | horizontal">
  <z-carousel-content>
    <z-carousel-item>...</z-carousel-item>
    <z-carousel-item>...</z-carousel-item>
    <z-carousel-item>...</z-carousel-item>
  </z-carousel-content>
</z-carousel>
Copy

api

Listen for (zInited) to capture the Embla Carousel instance and (zSelected) to react to slide changes, reading selectedScrollSnap() and scrollSnapList().length off it.
1
2
3
4
5

Slide 1 of 0

import { ChangeDetectionStrategy, Component, signal } from '@angular/core';

import type { EmblaCarouselType } from 'embla-carousel';

import { ZardCardImports } from '@/shared/components/card/card.imports';
import { ZardCarouselImports } from '@/shared/components/carousel/carousel.imports';

@Component({
  selector: 'z-demo-carousel-api',
  imports: [ZardCarouselImports, ZardCardImports],
  template: `
    <div class="w-full max-w-48 sm:max-w-xs">
      <z-carousel (zInited)="onInited($event)" (zSelected)="onSelected()">
        <z-carousel-content>
          @for (slide of slides; track slide) {
            <z-carousel-item>
              <div class="p-1">
                <z-card>
                  <z-card-content class="flex aspect-square items-center justify-center p-6">
                    <span class="text-4xl font-semibold">{{ slide }}</span>
                  </z-card-content>
                </z-card>
              </div>
            </z-carousel-item>
          }
        </z-carousel-content>
      </z-carousel>
      <p class="text-muted-foreground py-2 text-center text-sm">Slide {{ current() }} of {{ count() }}</p>
    </div>
  `,
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ZardDemoCarouselApiComponent {
  protected slides = ['1', '2', '3', '4', '5'];

  protected readonly current = signal(1);
  protected readonly count = signal(0);

  #emblaApi?: EmblaCarouselType;

  onInited(emblaApi: EmblaCarouselType): void {
    this.#emblaApi = emblaApi;
    this.count.set(emblaApi.scrollSnapList().length);
    this.current.set(emblaApi.selectedScrollSnap() + 1);
  }

  onSelected(): void {
    if (!this.#emblaApi) {
      return;
    }
    this.current.set(this.#emblaApi.selectedScrollSnap() + 1);
  }
}

plugins

Use ZardCarouselPluginsService to create an Embla plugin, such as autoplay, and pass it to [zPlugins].
1
2
3
4
5
import { ChangeDetectionStrategy, Component, inject, type OnInit, signal } from '@angular/core';

import type { EmblaPluginType } from 'embla-carousel';

import { ZardCardImports } from '@/shared/components/card/card.imports';
import { ZardCarouselPluginsService } from '@/shared/components/carousel/carousel-plugins.service';
import { ZardCarouselImports } from '@/shared/components/carousel/carousel.imports';

@Component({
  selector: 'z-demo-carousel-plugins',
  imports: [ZardCarouselImports, ZardCardImports],
  template: `
    <div class="w-full max-w-48 sm:max-w-xs">
      <z-carousel [zPlugins]="plugins()" [zOptions]="{ loop: true }">
        <z-carousel-content>
          @for (slide of slides; track slide) {
            <z-carousel-item>
              <div class="p-1">
                <z-card>
                  <z-card-content class="flex aspect-square items-center justify-center p-6">
                    <span class="text-4xl font-semibold">{{ slide }}</span>
                  </z-card-content>
                </z-card>
              </div>
            </z-carousel-item>
          }
        </z-carousel-content>
      </z-carousel>
    </div>
  `,
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ZardDemoCarouselPluginsComponent implements OnInit {
  readonly #pluginsService = inject(ZardCarouselPluginsService);

  protected slides = ['1', '2', '3', '4', '5'];
  protected readonly plugins = signal<EmblaPluginType[]>([]);

  ngOnInit(): void {
    void this.#loadAutoplay();
  }

  async #loadAutoplay(): Promise<void> {
    const autoplay = await this.#pluginsService.createAutoplayPlugin({ delay: 2000, stopOnInteraction: true });
    this.plugins.set([autoplay]);
  }
}

rtl

To enable RTL support in zard/ui, see the RTL configuration guide.
١
٢
٣
٤
٥
// You will notice this example is rendered inside a wrapper that sets the dir and data-lang attributes, and
// reads its text from a translations helper. This is because this site is not RTL by default.
// In your application you won't need either: set dir="rtl" on <html> (or use the CDK Dir directive on a subtree)
// and write the text of your own language. See the RTL guide: https://zardui.com/docs/rtl

import { ChangeDetectionStrategy, Component, computed } from '@angular/core';

import { ZardCardImports } from '@/shared/components/card/card.imports';
import { ZardCarouselImports } from '@/shared/components/carousel/carousel.imports';
import { injectRtlTranslation, type ZardRtlTranslations } from '@/shared/demo-utils/rtl-translation';

/** The carousel has no text of its own; each language only picks the numerals of the slides. */
const translations: ZardRtlTranslations<{ locale: string }> = {
  en: { dir: 'ltr', values: { locale: 'en-US' } },
  ar: { dir: 'rtl', values: { locale: 'ar-EG' } },
  he: { dir: 'rtl', values: { locale: 'he-IL' } },
};

@Component({
  selector: 'z-demo-carousel-rtl',
  imports: [ZardCarouselImports, ZardCardImports],
  template: `
    <!-- Embla and the arrows follow the direction: ArrowLeft goes to the next slide in RTL. -->
    <div class="w-full max-w-48 sm:max-w-xs">
      <z-carousel>
        <z-carousel-content>
          @for (slide of slides(); track slide.index) {
            <z-carousel-item>
              <div class="p-1">
                <z-card>
                  <z-card-content class="flex aspect-square items-center justify-center p-6">
                    <span class="text-4xl font-semibold">{{ slide.label }}</span>
                  </z-card-content>
                </z-card>
              </div>
            </z-carousel-item>
          }
        </z-carousel-content>
      </z-carousel>
    </div>
  `,
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ZardDemoCarouselRtlComponent {
  protected readonly i18n = injectRtlTranslation(translations);

  protected readonly slides = computed(() => {
    const formatter = new Intl.NumberFormat(this.i18n.t().locale);
    return Array.from({ length: 5 }, (_, index) => ({ index, label: formatter.format(index + 1) }));
  });
}

API Reference

z-carouselComponent

A carousel component with slide controls and swipe gesture support. Add `#ref="zCarousel"` on the element to call its public methods. It follows the direction of the page or of a CDK `Dir` subtree: in RTL the arrows swap sides and ArrowLeft goes to the next slide.

PropertyDescriptionTypeDefault
[class] Additional CSS classes ClassValue ''
[zOptions] Embla Carousel configuration options, including `align` ('start' | 'center' | 'end'), `loop`, and `direction` ('ltr' | 'rtl'). `axis` is derived from `zOrientation` and should not be set here EmblaOptionsType {loop: false}
[zPlugins] Embla Carousel plugins, e.g. an autoplay plugin created with `ZardCarouselPluginsService` EmblaPluginType[] []
[zOrientation] Carousel orientation 'horizontal' | 'vertical' 'horizontal'
[zControls] Navigation controls rendered inside the carousel 'button' | 'dot' | 'none' 'button'
(zInited) Emits the Embla Carousel instance once initialized; read the current slide (`selectedScrollSnap()`) and total slide count (`scrollSnapList().length`) off it EventEmitter<EmblaCarouselType> -
(zSelected) Emitted when the selected slide changes EventEmitter<void> -
slidePrevious() Public method that scrolls to the previous slide () => void -
slideNext() Public method that scrolls to the next slide () => void -
goTo(index) Public method that scrolls to the slide at the given index (index: number) => void -

z-carousel-contentComponent

The content container for the carousel.

PropertyDescriptionTypeDefault
[class] Additional CSS classes ClassValue ''

z-carousel-itemComponent

An individual carousel item.

PropertyDescriptionTypeDefault
[class] Additional CSS classes ClassValue ''
github iconwhatsapp icondiscord iconX icon

Made with ❤ in Brazil. Open source and available on GitHub .