Run the CLI
Use the CLI to add the component to your project.
npx @ngzard/ui@latest add cardDisplays a card with header, content, and footer.
import { Component } from '@angular/core';
import { ZardButtonComponent } from '@/shared/components/button/button.component';
import { ZardCardComponent } from '@/shared/components/card/card.component';
import { generateId } from '@/shared/utils/merge-classes';
@Component({
selector: 'z-demo-card-default',
imports: [ZardCardComponent, ZardButtonComponent],
template: `
<z-card
class="w-full md:w-94"
zTitle="Login to your account"
zDescription="Enter your email below to login to your account"
zAction="Sign Up"
(zActionClick)="onActionClick()"
>
<div class="space-y-4">
<div class="space-y-2">
<label
[for]="idEmail"
class="text-sm leading-none font-medium peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
>
Email
</label>
<input
[id]="idEmail"
type="email"
placeholder="m@example.com"
class="border-input bg-background ring-offset-background placeholder:text-muted-foreground focus-visible:ring-ring flex h-10 w-full rounded-md border px-3 py-2 text-sm file:border-0 file:bg-transparent file:text-sm file:font-medium focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50"
required
/>
</div>
<div class="space-y-2">
<div class="flex items-center">
<label
[for]="idPassword"
class="text-sm leading-none font-medium peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
>
Password
</label>
<a href="#" class="ml-auto text-sm underline-offset-4 hover:underline">Forgot your password?</a>
</div>
<input
[id]="idPassword"
type="password"
class="border-input bg-background ring-offset-background placeholder:text-muted-foreground focus-visible:ring-ring flex h-10 w-full rounded-md border px-3 py-2 text-sm file:border-0 file:bg-transparent file:text-sm file:font-medium focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50"
required
/>
</div>
</div>
<div card-footer class="flex w-full flex-col gap-2">
<z-button zType="default">Login</z-button>
<z-button zType="outline">Login with Google</z-button>
</div>
</z-card>
`,
})
export class ZardDemoCardDefaultComponent {
protected readonly idEmail = generateId('email');
protected readonly idPassword = generateId('password');
protected onActionClick(): void {
alert('Redirect to Sign Up');
}
}
Use the CLI to add the component to your project.
npx @ngzard/ui@latest add cardpnpm dlx @ngzard/ui@latest add cardyarn dlx @ngzard/ui@latest add cardbunx @ngzard/ui@latest add cardCreate the component directory structure and add the following files to your project.
import {
ChangeDetectionStrategy,
Component,
computed,
input,
output,
type TemplateRef,
ViewEncapsulation,
} from '@angular/core';
import type { ClassValue } from 'clsx';
import { ZardButtonComponent } from '@/shared/components/button/button.component';
import { ZardStringTemplateOutletDirective } from '@/shared/core/directives/string-template-outlet/string-template-outlet.directive';
import { generateId, mergeClasses } from '@/shared/utils/merge-classes';
import { cardBodyVariants, cardFooterVariants, cardHeaderVariants, cardVariants } from './card.variants';
@Component({
selector: 'z-card',
imports: [ZardStringTemplateOutletDirective, ZardButtonComponent],
template: `
@let title = zTitle();
@if (title) {
<div [class]="headerClasses()" data-slot="card-header">
<div class="leading-none font-semibold" [id]="titleId" data-slot="card-title">
<ng-container *zStringTemplateOutlet="title">{{ title }}</ng-container>
</div>
@let description = zDescription();
@if (description) {
<div class="text-muted-foreground text-sm" [id]="descriptionId" data-slot="card-description">
<ng-container *zStringTemplateOutlet="description">{{ description }}</ng-container>
</div>
}
@let action = zAction();
@if (action) {
<button
z-button
type="button"
zType="link"
class="col-start-2 row-span-2 row-start-1 self-start justify-self-end"
data-slot="card-action"
(click)="onClick()"
>
{{ action }}
</button>
}
</div>
}
<div [class]="bodyClasses()" data-slot="card-content">
<ng-content />
</div>
<div [class]="footerClasses()" data-slot="card-footer">
<ng-content select="[card-footer]" />
</div>
`,
styles: `
[data-slot='card-footer']:empty {
display: none;
}
`,
changeDetection: ChangeDetectionStrategy.OnPush,
encapsulation: ViewEncapsulation.None,
host: {
'data-slot': 'card',
'[class]': 'classes()',
'[attr.aria-labelledby]': 'zTitle() ? titleId : null',
'[attr.aria-describedby]': 'zDescription() ? descriptionId : null',
},
exportAs: 'zCard',
})
export class ZardCardComponent {
readonly class = input<ClassValue>('');
readonly zFooterBorder = input(false);
readonly zHeaderBorder = input(false);
readonly zAction = input('');
readonly zDescription = input<string | TemplateRef<void>>();
readonly zTitle = input<string | TemplateRef<void>>();
readonly zActionClick = output<void>();
protected readonly titleId = generateId('card-title');
protected readonly descriptionId = generateId('card-description');
protected readonly classes = computed(() => mergeClasses(cardVariants(), this.class()));
protected readonly bodyClasses = computed(() => mergeClasses(cardBodyVariants()));
protected readonly footerClasses = computed(() =>
mergeClasses(cardFooterVariants(), this.zFooterBorder() ? 'border-t' : ''),
);
protected readonly headerClasses = computed(() =>
mergeClasses(cardHeaderVariants(), this.zHeaderBorder() ? 'border-b' : ''),
);
protected onClick(): void {
this.zActionClick.emit();
}
}
import { cva } from 'class-variance-authority';
import { mergeClasses } from '@/shared/utils/merge-classes';
export const cardVariants = cva('bg-card text-card-foreground flex flex-col gap-6 rounded-xl border py-6 shadow-sm');
export const cardHeaderVariants = cva(
mergeClasses(
'@container/card-header grid auto-rows-min grid-rows-[auto_auto] items-start gap-2 px-6',
'has-data-[slot=card-action]:grid-cols-[1fr_auto] [.border-b]:pb-6',
),
);
export const cardActionVariants = cva('col-start-2 row-span-2 row-start-1 self-start justify-self-end');
export const cardBodyVariants = cva('px-6');
export const cardFooterVariants = cva('flex flex-col gap-2 items-center px-6 [.border-t]:pt-6');
export * from './card.component';
export * from './card.variants';
import { Component } from '@angular/core';
import { ZardButtonComponent } from '@/shared/components/button/button.component';
import { ZardCardComponent } from '@/shared/components/card/card.component';
import { generateId } from '@/shared/utils/merge-classes';
@Component({
selector: 'z-demo-card-default',
imports: [ZardCardComponent, ZardButtonComponent],
template: `
<z-card
class="w-full md:w-94"
zTitle="Login to your account"
zDescription="Enter your email below to login to your account"
zAction="Sign Up"
(zActionClick)="onActionClick()"
>
<div class="space-y-4">
<div class="space-y-2">
<label
[for]="idEmail"
class="text-sm leading-none font-medium peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
>
Email
</label>
<input
[id]="idEmail"
type="email"
placeholder="m@example.com"
class="border-input bg-background ring-offset-background placeholder:text-muted-foreground focus-visible:ring-ring flex h-10 w-full rounded-md border px-3 py-2 text-sm file:border-0 file:bg-transparent file:text-sm file:font-medium focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50"
required
/>
</div>
<div class="space-y-2">
<div class="flex items-center">
<label
[for]="idPassword"
class="text-sm leading-none font-medium peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
>
Password
</label>
<a href="#" class="ml-auto text-sm underline-offset-4 hover:underline">Forgot your password?</a>
</div>
<input
[id]="idPassword"
type="password"
class="border-input bg-background ring-offset-background placeholder:text-muted-foreground focus-visible:ring-ring flex h-10 w-full rounded-md border px-3 py-2 text-sm file:border-0 file:bg-transparent file:text-sm file:font-medium focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50"
required
/>
</div>
</div>
<div card-footer class="flex w-full flex-col gap-2">
<z-button zType="default">Login</z-button>
<z-button zType="outline">Login with Google</z-button>
</div>
</z-card>
`,
})
export class ZardDemoCardDefaultComponent {
protected readonly idEmail = generateId('email');
protected readonly idPassword = generateId('password');
protected onActionClick(): void {
alert('Redirect to Sign Up');
}
}