I have an ad system with two categories: TOP and BASIC. Ads are loaded from the backend in random order, returning a paginated object where TOP ads come first and BASIC ads come after. I want to display them in a grid, using either CSS Grid or Flexbox (or any better alternative someone may suggest—I’d really appreciate it).
I need the following layout conditions within the same grid or flexbox container:
TOP ads:
- Mobile: 2 per row
- Tablet: 3 per row
- Desktop: 4 per row
- Big screen: 5 per row
BASIC ads:
- Mobile: 2 per row
- Tablet: 3 per row
- Desktop: 4 per row
- Big screen: 5 per row
This is what I’ve implemented so far, but the rows don’t align properly some end up larger than others, and vice versa.
//styles.tsexport const AdsGrid = styled.div` display: flex; flex-wrap: wrap; gap: 16px; padding: 1rem; width: 100%;`;export const GridItem = styled.div<{ isTop?: boolean }>` transition: transform 0.3s ease;&:hover { transform: scale(1.02); } // Mobile (default) flex: ${({ isTop }) => (isTop ? "31.33%" : "15%")}; @media (min-width: ${({ theme }) => theme.breakpoints.md}) { width: ${({ isTop }) => (isTop ? "33.33%" : "25%")}; // tablet } @media (min-width: ${({ theme }) => theme.breakpoints.lg}) { width: ${({ isTop }) => (isTop ? "25%" : "20%")}; // desktop } @media (min-width: ${({ theme }) => theme.breakpoints.xxl}) { width: ${({ isTop }) => (isTop ? "31.33%" : "15%")}; // big screen }`;and the component:
import { useModelProfileContext } from "@/context/ModelProfileContext";import InfiniteScroll from "react-infinite-scroll-component";import { AdCardSkeleton, ListSkeleton } from "@/components";import { AdsContainerStyles, AdsGrid } from "@/components/home/ads/styles";import { AdCard } from "@/components/home";const gridConfig = { gutter: 16, xs: 3, sm: 2, md: 3, lg: 4, xl: 4, xxl: 6,};export default function AdsWrapper() { const { data, hasMore, loadMore, isLoading } = useModelProfileContext(); if (isLoading && data.length === 0) { return (<ListSkeleton count={12} gridConfig={gridConfig}><AdCardSkeleton /></ListSkeleton> ); } return (<AdsContainerStyles><InfiniteScroll next={loadMore} hasMore={hasMore} loader={<ListSkeleton count={6} gridConfig={gridConfig}><AdCardSkeleton /></ListSkeleton> } dataLength={data.length} className="overflow-hidden"><AdsGrid> {data.map((model) => (<AdCard key={model.id} model={model} /> ))}</AdsGrid></InfiniteScroll></AdsContainerStyles> );}and AdCard.tsx
"use client";import { GridItem, IsTopAdTagStyles, NewProfileTagStyles,} from "@/components/home/ads/styles";import { Badge, Flex, Typography } from "antd";import Link from "next/link";import { CustomCard, Text } from "@/components/antd";import { AdCover } from "@/components/home";import { RiVerifiedBadgeFill } from "react-icons/ri";import { formatSentences, sectionColor } from "@/utils/helper";import { TfiLocationPin } from "react-icons/tfi";import { Model } from "@/types/models";export default function AdCard({ model }: { model: Model }) { const sectionHex = model?.section && sectionColor(model.section); const isNew = model?.isNew || false; const isTop = model?.isTop || false; return (<GridItem key={model.id} isTop={isTop} className="intro-x"><Badge.Ribbon text={model?.package?.name} placement="start" style={{ zIndex: 1000 }} /><Link href={`/profile/${model.slug}`}><CustomCard cover={<AdCover file={model?.ad?.cover?.thumbnail} />} styles={{ body: { minHeight: 126, padding: 10, backgroundColor: sectionHex, }, cover: { overflow: "hidden", }, }}> {isNew && (<NewProfileTagStyles>Novedad</NewProfileTagStyles> )} {isTop && <IsTopAdTagStyles>Novedad</IsTopAdTagStyles>}<Flex justify="space-between" vertical><Flex justify="center" align="center" gap={5}><RiVerifiedBadgeFill /><Text size="md" className="bold uppercase" ellipsis> {model.alias}</Text></Flex><Typography.Paragraph style={{ textAlign: "center", }} ellipsis={{ rows: 2, suffix: ".", }}> {formatSentences(model?.ad?.content || "")}</Typography.Paragraph><Flex justify="center" align="start" gap={5}><TfiLocationPin style={{ fontSize: 14 }} /><Text size="xs" className="uppercase bold" ellipsis> {model?.location?.cityName}</Text></Flex></Flex></CustomCard></Link></GridItem> );}```[Image][1] [1]: https://i.sstatic.net/Kn6Rs3JG.png