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:
-
Render 10 (or more) custom cards without duplicating them manually in the editor.
-
Navigate between them with side arrow buttons (carousel/slider behavior).
-
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
NewsTileCardwith 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).
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
-
Load 10+ cards (ideally from a list input or repeat) instead of adding cards one by one in the editor.
-
Side arrows to move next/prev (either scroll‑snap or translateX is fine).
-
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
-
Repeating cards: What’s the best practice in Builder (Angular) to render many instances of a custom component?
- Could I use a custom wrapper with an
itemslist input registered in Builder?
- Could I use a custom wrapper with an
-
Carousel/Arrows: Is there a recommended way to add arrow navigation around repeated custom components inside Builder content?
- Any example of a custom carousel wrapper registered with Builder?
-
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?
-
Should we expose CSS variables (e.g.,
--news-card-image-h) and let editors set those per breakpoint on the block containing the custom component? -
Are there other patterns that work well with Angular’s ViewEncapsulation?
-