# Angular + Builder.io: How to render 10+ custom cards with arrow navigation (carousel) and make the card internals responsive?

**URL:** <https://forum.builder.io/t/angular-builder-io-how-to-render-10-custom-cards-with-arrow-navigation-carousel-and-make-the-card-internals-responsive/16229>\
**Category:** CMS\
**Tags:** angular\
**Created:** [March 19, 2026, 8:57am UTC](https://forum.builder.io/t/angular-builder-io-how-to-render-10-custom-cards-with-arrow-navigation-carousel-and-make-the-card-internals-responsive/16229 "2026-03-19T08:57:05Z")\
**Posts on this page:** 2\
**Page:** 1

<div class="post-metadata">

**Author:** ![LaviniaGavrilescu21](https://avatars.discourse-cdn.com/v4/letter/l/ba9def/32.png) [@LaviniaGavrilescu21](https://forum.builder.io/u/LaviniaGavrilescu21)\
**Post date:** [March 19, 2026, 8:57am UTC](https://forum.builder.io/t/angular-builder-io-how-to-render-10-custom-cards-with-arrow-navigation-carousel-and-make-the-card-internals-responsive/16229/1 "2026-03-19T08:57:05Z")

</div>

I’m using **Angular** with `@builder.io/sdk-angular` and a custom component that we registered in Builder as `NewsTileCard`. Right now I can drop 3 cards into a grid from Builder and they render fine in my page. I’d like to:

1. Render **10 (or more) custom cards** without duplicating them manually in the editor.

2. **Navigate** between them with side **arrow buttons** (carousel/slider behavior).

3. Make the **card internals responsive** (image height, min-height, fonts) with **per‑breakpoint styles** , not just the container.

### What I have

- A registered custom component `NewsTileCard` with inputs: `imageSrc`, `title`, `description`.

- A container component that renders `<builder-content>` and sets a 3‑column grid with `::ng-deep`.

- Media queries on the **container** work, but I’m struggling to apply breakpoint styles to the **internal card elements** from Builder (I’m currently only able to style the wrapper, not the child component internals).

```typescript
import type { RegisteredComponent } from '@builder.io/sdk-angular';
import { Component, Input } from '@angular/core';

@Component({
  selector: 'lib-builder-news-tile-card',
  standalone: true,
  template: `
    <article class="newsCard">
      <div class="newsCard__imageWrap">
        @if (imageSrc) {
        <img class="newsCard__img" [src]="imageSrc" alt="" />
        } @else {
        <div class="newsCard__imgPlaceholder"></div>
        }
      </div>

      @if (title) {
      <h3 class="newsCard__title">{{ title }}</h3>
      } @if (description) {
      <p class="newsCard__desc">{{ description }}</p>
      }
    </article>
  `,
  styles: [
    `
      .newsCard {
        display: flex;
        width: 100%;
        max-width: none;
        min-height: var(--news-card-min-h, 620px);
        height: 100%;
        padding: 10px 10px 22px 10px;
        flex-direction: column;
        align-items: center;
        gap: 12px;
        border-radius: 16px;
        background: transparent;
        backdrop-filter: blur(10px) saturate(180%);
        -webkit-backdrop-filter: blur(10px) saturate(180%);
        border: 1px solid rgba(255, 255, 255, 0.10);
        box-shadow: 1px 1px 0 0 rgba(255, 255, 255, 0.35) inset;
        box-sizing: border-box;
        transition:
          box-shadow 200ms ease,
          transform 200ms ease,
          background 200ms ease,
          border-color 200ms ease;
      }

      .newsCard:hover {
        transform: translateY(-1px);
        border-color: rgba(255, 255, 255, 0.22);
        background: transparent;
        box-shadow:
          1px 1px 0 0 rgba(255, 255, 255, 0.45) inset,
          0 0 22px rgba(34, 121, 224, 0.35),
          0 0 10px rgba(34, 121, 224, 0.22);
      }

      .newsCard__imageWrap {
        height: var(--news-card-image-h, 100px);
        align-self: stretch;
        border-radius: 12px;
        overflow: hidden;
        background: rgba(255, 255, 255, 0.08);
        position: relative;
      }

      .newsCard__img {
        width: 100%;
        height: 100%;
        object-fit: cover;
        display: block;
        transition: transform 250ms ease;
      }

      .newsCard:hover .newsCard__img {
        transform: scale(1.02);
      }

      .newsCard__imgPlaceholder {
        width: 100%;
        height: 100%;
        background: #d9d9d9;
      }

      .newsCard__title {
        align-self: stretch;
        margin: 0;
        color: #fff;
        font-family: Inter, sans-serif;
        font-size: 24px;
        font-weight: 500;
        line-height: 24px;
        transition:
          text-shadow 200ms ease,
          opacity 200ms ease;
      }

      .newsCard:hover .newsCard__title {
        text-shadow:
          0 0 18px rgba(34, 121, 224, 0.55),
          0 0 8px rgba(34, 121, 224, 0.35);
      }

      .newsCard__desc {
        align-self: stretch;
        margin: 0;
        color: #fff;
        font-family: Inter, sans-serif;
        font-size: 16px;
        font-weight: 300;
        line-height: 24px;
        max-height: 96px;
        overflow-y: auto;
        overflow-x: hidden;
        padding-right: 8px;
        scrollbar-width: thin;
        scrollbar-color: rgba(255, 255, 255, 0.35) rgba(255, 255, 255, 0.1);
        transition:
          text-shadow 200ms ease,
          opacity 200ms ease;
      }

      .newsCard:hover .newsCard__desc {
        text-shadow: 0 0 10px rgba(34, 121, 224, 0.25);
      }

      .newsCard__desc::-webkit-scrollbar {
        width: 8px;
      }

      .newsCard__desc::-webkit-scrollbar-track {
        background: rgba(255, 255, 255, 0.1);
        border-radius: 999px;
      }

      .newsCard__desc::-webkit-scrollbar-thumb {
        background: rgba(255, 255, 255, 0.35);
        border-radius: 999px;
        border: 2px solid rgba(255, 255, 255, 0.1);
        background-clip: padding-box;
      }

      .newsCard__desc::-webkit-scrollbar-thumb:hover {
        background: rgba(255, 255, 255, 0.55);
      }
    `
  ]
})
export class BuilderNewsTileCardComponent {
  @Input() imageSrc = '';
  @Input() title = '';
  @Input() description = '';
}

export const CUSTOM_COMPONENTS: RegisteredComponent[] = [
  {
    component: BuilderNewsTileCardComponent,
    name: 'NewsTileCard',
    inputs: [
      {
        name: 'imageSrc',
        type: 'file',
        allowedFileTypes: ['jpeg', 'jpg', 'png', 'svg', 'webp']
      },
      { name: 'title', type: 'string', defaultValue: 'Title' },
      { name: 'description', type: 'longText' }
    ]
  }
];

```

### What I’m trying to achieve

1. **Load 10+ cards** (ideally from a list input or repeat) instead of adding cards one by one in the editor.

2. **Side arrows** to move next/prev (either scroll‑snap or translateX is fine).

3. **Responsive card internals** controlled per breakpoint **from Builder** (e.g., different image height/min-height on mobile), not only with global CSS or container media queries.

### Questions

1. **Repeating cards** : What’s the best practice in Builder (Angular) to render many instances of a custom component?

2. **Carousel/Arrows** : Is there a recommended way to add **arrow navigation** around repeated custom components inside Builder content?

3. **Responsive styling inside the card** : Since Builder styles apply to the wrapper element, what’s your recommended pattern to let editors control **inner component styles per breakpoint**?

---

<div class="post-metadata">

**Author:** ![manish-sharma](https://avatars.discourse-cdn.com/v4/letter/m/8797f3/32.png) [@manish-sharma](https://forum.builder.io/u/manish-sharma)\
**Post date:** [March 19, 2026, 4:44pm UTC](https://forum.builder.io/t/angular-builder-io-how-to-render-10-custom-cards-with-arrow-navigation-carousel-and-make-the-card-internals-responsive/16229/2 "2026-03-19T16:44:23Z")

</div>

Hello @LaviniaGavrilescu21,

The recommended approach here is to register a wrapper component (e.g. `NewsCarousel`) with a `list` input instead of dropping `NewsTileCard` individually in the editor. That way editors manage one block, fill in as many items as they need, and your component handles the rendering:

```ts
{
  component: NewsCarouselComponent,
  name: 'NewsCarousel',
  inputs: [
    {
      name: 'items',
      type: 'list',
      subFields: [
        { name: 'imageSrc', type: 'file', allowedFileTypes: ['jpeg', 'jpg', 'png', 'svg', 'webp'] },
        { name: 'title', type: 'string', defaultValue: 'Title' },
        { name: 'description', type: 'longText' },
      ],
    },
    { name: 'visibleCount', type: 'number', defaultValue: 3 },
    { name: 'imageHeight', type: 'string', defaultValue: '200px' },
    { name: 'cardMinHeight', type: 'string', defaultValue: '620px' },
  ],
}

```

**Arrow navigation**  
No external library needed for basic carousel behavior. A `visibleCount` input + `slice()` on the list gets you there cleanly:

```ts
index = 0;

get visibleCards() {
  return this.items.slice(this.index, this.index + this.visibleCount);
}
prev() { this.index = Math.max(0, this.index - 1); }
next() { this.index = Math.min(this.items.length - this.visibleCount, this.index + 1); }

```

**Responsive card internals**  
This is a common pain point with Angular’s `ViewEncapsulation`. Builder’s per-breakpoint styles are written to the wrapper element it generates (e.g. `.builder-block-xxxxx`), so they won’t reach inside your component’s encapsulation boundary with `Emulated` mode.

The pattern we’d recommend is CSS variables — which you’re already using (`--news-card-image-h`, `--news-card-min-h`). Just expose them as string inputs and bind them inline:

```ts
@Input() imageHeight = '200px';
@Input() cardMinHeight = '620px';

```

```html
<article class="newsCard"
  [style.--news-card-image-h]="imageHeight"
  [style.--news-card-min-h]="cardMinHeight">

```

Editors can then set `imageHeight` to `200px` on desktop and `120px` on mobile using the breakpoint switcher in the inputs panel; no CSS knowledge required on their end.

This keeps encapsulation intact and avoids the class collision risk you’d get with `ViewEncapsulation.None`.

Hope that helps - let us know if you run into anything!
