Featured: Editorial team announces 2026 redesign
A short summary of the article — used to give the reader a taste of the content before they click through to the full piece.
Slot-based magazine layout — hero, filter bar, sidebar, and a mixed-size article grid with infinite scroll and a filter hook.
The layout assembly for the news-domain family. Generic over `T`; renders cards via a `renderItem(item, slot)` callback so the layout itself imports nothing from sibling registry components — composition happens at the consumer level. Pair with news-card (renderItem), filter-bar (filterBar slot), category-cloud + newsletter-signup (sidebar slot), and page-hero (hero slot) for the full kasder magazine experience. The companion `useMagazineFilter` hook gives the simple consumer one-line filter+page state; sophisticated consumers skip the hook and drive props from React Query / their router. Migration origin: kasder kas-social-front-v0 NewsMagazineGrid.tsx (~320-line component) → distilled to this slot-based shell + a 60-line filter hook. The original's filter logic / chip row / search / date picker / sidebar contents all moved into the dedicated sibling components.
pnpm dlx shadcn@latest init"registries": {
"@ilinxa": "https://ui.ilinxa.com/r/{name}.json"
}pnpm dlx shadcn@latest add @ilinxa/magazine-layoutAdd -fixtures for dummy data:
pnpm dlx shadcn@latest add @ilinxa/magazine-layout-fixturesA short summary of the article — used to give the reader a taste of the content before they click through to the full piece.
A short summary of the article — used to give the reader a taste of the content before they click through to the full piece.
Aug 11, 2026
A short summary of the article — used to give the reader a taste of the content before they click through to the full piece.
Aug 10, 2026
A short summary of the article — used to give the reader a taste of the content before they click through to the full piece.
Aug 9, 2026
A short summary of the article — used to give the reader a taste of the content before they click through to the full piece.
Aug 8, 2026
A short summary of the article — used to give the reader a taste of the content before they click through to the full piece.
Aug 7, 2026
A short summary of the article — used to give the reader a taste of the content before they click through to the full piece.
Aug 6, 2026
Reach for MagazineLayout when you need a magazine- style layout: optional hero band, optional filter row, descending- density card tower in a main column, optional sticky sidebar, infinite scroll. The layout is generic over your item type and slot-based — bring your own cards, hero, filter bar, sidebar.
import { MagazineLayout } from "@/components/magazine-layout";
<MagazineLayout<Article>
displayedItems={articles}
renderItem={(article, slot) => (
<ArticleCard article={article} variant={slot} />
)}
/>;slot is either "large" (lead article) or "medium" (the rest of the tower). Your renderer maps slot to a card variant.
Combines all four sibling components from the news family. The layout itself imports nothing from them — composition happens at the consumer level.
import { MagazineLayout, useMagazineFilter } from "@/components/magazine-layout";
import { NewsCard } from "@/components/news-card";
import { FilterBar } from "@/components/filter-bar";
import { CategoryCloud } from "@/components/category-cloud";
import { NewsletterSignup } from "@/components/newsletter-signup";
import { PageHero } from "@/components/page-hero";
const filtered = useMagazineFilter<Article>({
items: articles,
pageSize: 6,
isFeatured: (a) => a.featured,
filterPredicate: (a) => a.title.includes(search) && (cat ? a.category === cat : true),
sortComparator: (a, b) => +new Date(b.date) - +new Date(a.date),
});
<MagazineLayout<Article>
hero={<PageHero badge="News" title="Latest Stories" description="..." />}
filterBar={<FilterBar categories={categories} onChange={...} />}
sidebar={
<>
<CategoryCloud items={categories} value={cat} onChange={setCat} title="Categories" />
<NewsletterSignup onSubmit={subscribe} />
</>
}
displayedItems={filtered.displayedItems}
featuredItem={filtered.featuredItem}
hasMore={filtered.hasMore}
isLoading={filtered.isLoading}
onLoadMore={filtered.loadMore}
renderItem={(article, slot) => (
<NewsCard item={article} variant={slot} href={`/news/${article.id}`} />
)}
/>;For consumers who don't need server-driven filtering / pagination, useMagazineFilter derives all the props the layout expects from your full items array + filter / sort / feature predicates.
const {
displayedItems,
featuredItem,
hasMore,
isLoading,
filteredCount,
loadMore,
reset,
} = useMagazineFilter<Article>({
items,
pageSize: 6,
isFeatured: (a) => a.featured,
filterPredicate: (a) => /* filter */,
sortComparator: (a, b) => /* sort */,
simulatedLoadingMs: 500, // optional artificial delay for demos
});sidebar — main column expands to full width.hero and filterBar — bare layout for documentation indexes or simple lists.emptyState for a custom empty fallback; otherwise the default labels.emptyStateText renders.renderFeatured for a different visual on the featured item (e.g., a hero card variant) vs. the in-tower large.Skip the companion hook. Drive displayedItems / hasMore / isLoading / onLoadMore from your data layer (React Query, Tanstack Router, etc.) directly:
const { data, fetchNextPage, hasNextPage, isFetching } = useInfiniteQuery(...);
<MagazineLayout
displayedItems={data?.pages.flatMap(p => p.items) ?? []}
hasMore={!!hasNextPage}
isLoading={isFetching}
onLoadMore={() => fetchNextPage()}
renderItem={...}
/>;<aside> for landmark semantics.aria-live="polite" with a visually-hidden text label.aria-live="polite".