Documentation

Customization

Customize Angular AI Kit components to match your design system.

Customization Options

Multiple ways to customize components

Angular AI Kit provides several customization approaches:

1. Custom Classes

Use the customClasses input to add Tailwind classes

2. CSS Variables

Override CSS custom properties for global theming

3. Wrapper Components

Create wrapper components with custom behavior

4. DI Tokens

Provide configuration via dependency injection

Using Custom Classes

The simplest way to customize appearance

Most components accept a customClasses input for additional Tailwind classes:

1<!-- Using customClasses input -->2<ai-user-message3  [content]="message.content"4  customClasses="rounded-xl shadow-lg"5/>6 7<ai-response8  [content]="response.content"9  customClasses="border-l-4 border-primary pl-4"10/>11 12<ai-chat-input13  placeholder="Ask anything..."14  customClasses="bg-muted/50"15/>

Tips

  • Custom classes are merged with default classes
  • Use Tailwind's ! prefix for specificity if needed
  • Combine with CSS variables for full control

Creating Wrapper Components

Extend functionality with custom wrappers

Wrap existing components to add custom headers, footers, or behavior:

1// custom-message.component.ts2import { Component, input } from '@angular/core';3import { AiResponseComponent } from '@angular-ai-kit/core';4 5@Component({6  selector: 'app-custom-message',7  imports: [AiResponseComponent],8  template: `9    <div class="my-custom-wrapper">10      <!-- Custom header -->11      <div class="flex items-center gap-2 mb-2">12        <img [src]="avatarUrl()" class="w-8 h-8 rounded-full" />13        <span class="font-semibold">{{ name() }}</span>14      </div>15 16      <!-- Use original component -->17      <ai-response18        [content]="content()"19        [customClasses]="responseClasses"20      />21 22      <!-- Custom footer -->23      <div class="mt-2 text-xs text-muted-foreground">24        {{ timestamp() | date:'short' }}25      </div>26    </div>27  `,28  styles: `29    .my-custom-wrapper {30      padding: 1rem;31      border-radius: 0.75rem;32      background: var(--card);33    }34  `,35})36export class CustomMessageComponent {37  content = input.required<string>();38  name = input('Assistant');39  avatarUrl = input('/avatar.png');40  timestamp = input(new Date());41 42  readonly responseClasses = 'prose-sm';43}

Custom Action Buttons

Add your own actions to messages

Create custom action components and inject them via slots:

1// custom-actions.component.ts2import { Component, output } from '@angular/core';3import { NgIcon, provideIcons } from '@ng-icons/core';4import {5  lucideShare,6  lucideBookmark,7  lucideFlag,8} from '@ng-icons/lucide';9import { HlmIconImports } from '@angular-ai-kit/spartan-ui/icon';10 11@Component({12  selector: 'app-custom-actions',13  imports: [NgIcon, HlmIconImports],14  viewProviders: [provideIcons({ lucideShare, lucideBookmark, lucideFlag })],15  template: `16    <div class="flex items-center gap-1">17      <button18        class="p-2 rounded hover:bg-accent"19        title="Share"20        (click)="share.emit()"21      >22        <ng-icon hlm name="lucideShare" size="sm" />23      </button>24 25      <button26        class="p-2 rounded hover:bg-accent"27        title="Bookmark"28        (click)="bookmark.emit()"29      >30        <ng-icon hlm name="lucideBookmark" size="sm" />31      </button>32 33      <button34        class="p-2 rounded hover:bg-accent text-destructive"35        title="Report"36        (click)="report.emit()"37      >38        <ng-icon hlm name="lucideFlag" size="sm" />39      </button>40    </div>41  `,42})43export class CustomActionsComponent {44  share = output<void>();45  bookmark = output<void>();46  report = output<void>();47}48 49// Usage in parent50@Component({51  template: `52    <ai-response [content]="content">53      <app-custom-actions54        slot="actions"55        (share)="handleShare()"56        (bookmark)="handleBookmark()"57        (report)="handleReport()"58      />59    </ai-response>60  `,61})62export class ParentComponent {}

Custom Markdown Rendering

Override default markdown styles

Customize how markdown content is rendered:

1// custom-markdown.component.ts2import { Component, input } from '@angular/core';3import { MarkdownRendererComponent } from '@angular-ai-kit/core';4 5@Component({6  selector: 'app-custom-markdown',7  imports: [MarkdownRendererComponent],8  template: `9    <div class="custom-markdown-wrapper" [class]="wrapperClass()">10      <ai-markdown-renderer11        [content]="content()"12        [enableCodeHighlight]="true"13      />14    </div>15  `,16  styles: `17    .custom-markdown-wrapper {18      /* Override heading styles */19      :global(h1, h2, h3) {20        color: var(--primary);21        border-bottom: 2px solid var(--border);22        padding-bottom: 0.5rem;23      }24 25      /* Custom code block styling */26      :global(pre) {27        border-radius: 0.75rem;28        border: 1px solid var(--border);29      }30 31      /* Custom link styling */32      :global(a) {33        color: var(--primary);34        text-decoration: underline;35        text-underline-offset: 2px;36      }37 38      /* Custom list styling */39      :global(ul) {40        list-style-type: square;41      }42    }43  `,44})45export class CustomMarkdownComponent {46  content = input.required<string>();47  wrapperClass = input('');48}

Custom Code Block

Add features like run button and copy feedback

Create an enhanced code block with additional features:

1// custom-code-block.component.ts2import { Component, input, output, computed } from '@angular/core';3import { CodeBlockComponent } from '@angular-ai-kit/core';4import { NgIcon, provideIcons } from '@ng-icons/core';5import { lucidePlay, lucideCopy, lucideCheck } from '@ng-icons/lucide';6 7@Component({8  selector: 'app-custom-code-block',9  imports: [CodeBlockComponent, NgIcon],10  viewProviders: [provideIcons({ lucidePlay, lucideCopy, lucideCheck })],11  template: `12    <div class="custom-code-block">13      <!-- Custom header -->14      <div class="flex items-center justify-between px-4 py-2 bg-muted rounded-t-lg">15        <span class="text-sm font-mono text-muted-foreground">16          {{ language() }}17        </span>18        <div class="flex gap-2">19          @if (isRunnable()) {20            <button21              class="flex items-center gap-1 text-sm text-primary hover:underline"22              (click)="run.emit(code())"23            >24              <ng-icon name="lucidePlay" size="sm" />25              Run26            </button>27          }28          <button29            class="flex items-center gap-1 text-sm hover:underline"30            (click)="copyCode()"31          >32            <ng-icon [name]="copied() ? 'lucideCheck' : 'lucideCopy'" size="sm" />33            {{ copied() ? 'Copied!' : 'Copy' }}34          </button>35        </div>36      </div>37 38      <!-- Code block -->39      <ai-code-block40        [code]="code()"41        [language]="language()"42        [showLineNumbers]="showLineNumbers()"43      />44    </div>45  `,46})47export class CustomCodeBlockComponent {48  code = input.required<string>();49  language = input('typescript');50  showLineNumbers = input(true);51 52  run = output<string>();53 54  // Runnable languages55  isRunnable = computed(() => {56    const runnable = ['javascript', 'typescript', 'python'];57    return runnable.includes(this.language());58  });59 60  // Copy state61  private _copied = signal(false);62  copied = this._copied.asReadonly();63 64  copyCode() {65    navigator.clipboard.writeText(this.code());66    this._copied.set(true);67    setTimeout(() => this._copied.set(false), 2000);68  }69}

Custom Input Component

Add model selection and character count

Extend the chat input with model selection and other features:

1// custom-input.component.ts2import { Component, input, output, signal, computed } from '@angular/core';3import { FormsModule } from '@angular/forms';4import { ChatInputComponent } from '@angular-ai-kit/core';5import { HlmSelectImports } from '@angular-ai-kit/spartan-ui/select';6 7interface Model {8  id: string;9  name: string;10  description: string;11}12 13@Component({14  selector: 'app-custom-input',15  imports: [FormsModule, ChatInputComponent, HlmSelectImports],16  template: `17    <div class="custom-input-container">18      <!-- Model selector -->19      <div class="flex items-center gap-2 mb-2">20        <label class="text-sm text-muted-foreground">Model:</label>21        <select22          hlmSelect23          [(ngModel)]="selectedModel"24          class="w-48"25        >26          @for (model of models(); track model.id) {27            <option [value]="model.id">{{ model.name }}</option>28          }29        </select>30      </div>31 32      <!-- Main input -->33      <ai-chat-input34        [placeholder]="placeholder()"35        [disabled]="disabled()"36        [maxLength]="maxLength()"37        (messageSubmit)="handleSubmit($event)"38      />39 40      <!-- Character count -->41      <div class="flex justify-between mt-1 text-xs text-muted-foreground">42        <span>{{ charCount() }} / {{ maxLength() }}</span>43        <span>Using: {{ selectedModelName() }}</span>44      </div>45    </div>46  `,47})48export class CustomInputComponent {49  placeholder = input('Type your message...');50  disabled = input(false);51  maxLength = input(4000);52 53  models = input<Model[]>([54    { id: 'gpt-4', name: 'GPT-4', description: 'Most capable' },55    { id: 'gpt-3.5', name: 'GPT-3.5', description: 'Fast & cheap' },56    { id: 'claude-3', name: 'Claude 3', description: 'Best for writing' },57  ]);58 59  messageSubmit = output<{ content: string; model: string }>();60 61  selectedModel = signal('gpt-4');62  charCount = signal(0);63 64  selectedModelName = computed(() => {65    const model = this.models().find(m => m.id === this.selectedModel());66    return model?.name ?? 'Unknown';67  });68 69  handleSubmit(content: string) {70    this.messageSubmit.emit({71      content,72      model: this.selectedModel(),73    });74  }75}

Configuration via DI Tokens

Global configuration using dependency injection

Provide global configuration using Angular's dependency injection:

1// Customize via DI tokens2import {3  AI_AVATAR_CONFIG,4  AI_CODE_HIGHLIGHT_CONFIG,5  AI_MARKDOWN_CONFIG,6} from '@angular-ai-kit/core';7 8@Component({9  providers: [10    {11      provide: AI_AVATAR_CONFIG,12      useValue: {13        userAvatar: '/avatars/user.png',14        assistantAvatar: '/avatars/bot.png',15        showAvatar: true,16        size: 'md',17      },18    },19    {20      provide: AI_CODE_HIGHLIGHT_CONFIG,21      useValue: {22        theme: 'github-dark',23        showLineNumbers: true,24        wrapLongLines: false,25      },26    },27    {28      provide: AI_MARKDOWN_CONFIG,29      useValue: {30        sanitize: true,31        linkTarget: '_blank',32        enableGfm: true,33      },34    },35  ],36})37export class ChatModule {}

Available Tokens

  • AI_AVATAR_CONFIG - Avatar settings
  • AI_CODE_HIGHLIGHT_CONFIG - Code highlighting
  • AI_MARKDOWN_CONFIG - Markdown rendering
  • AI_THEME_CONFIG - Theme settings

Best Practices

Tips for effective customization

Start Simple

  • Use customClasses first
  • Then CSS variables for theming
  • Create wrappers for complex needs

Stay Consistent

  • Use your design system tokens
  • Create reusable wrapper components
  • Document your customizations

Test Thoroughly

  • Test in light and dark mode
  • Test responsive behavior
  • Test accessibility (keyboard, screen readers)

Keep Updated

  • Avoid relying on internal classes
  • Use public APIs only
  • Check changelog on updates

Next Steps

Explore more customization options: