kapwa
componentarticlesource: ok · hit
Article
Article card and content components for displaying news, blog posts, and long-form content.
npx shadcn add "https://kapwa-two.vercel.app/r/article.json"free
Where it goes
blog-list
Tags auto-generated by rules on 9/24/2026 — empty means no evidence, not lack of support.
Dependencies
Bundle weight · lightRegistry dependencies
cardbutton
Files1 · source cached 10/14/2026, 7:24:14 AM
src/lib/kapwa/article/index.tsx
import { Card, CardImage, CardContent } from '@kapwa/card';
import { Button } from '@kapwa/button';
type ArticleCardItem = {
id: number;
image: string;
imageAlt: string;
category: string;
title: string;
description: string;
hasOverlay?: boolean;
};
type ArticleContentSection = {
id: number;
type: 'heading' | 'paragraph';
level?: 1 | 2 | 3;
content: string;
};
const ArticleCard = ({ article }: { article: ArticleCardItem }) => {
return (
<Card>
{article.hasOverlay ? (
<div className='relative'>
<CardImage src={article.image} alt={article.imageAlt} />
<div className='absolute inset-0 bg-linear-to-t from-black/75 to-transparent flex items-end p-6'>
<div className='text-white'>
<span className='inline-block px-2 py-1 text-xs font-medium rounded-sm bg-white/20 mb-2'>
{article.category}
</span>
<h3 className='text-xl font-semibold mb-2'>{article.title}</h3>
<p className='text-white/80'>{article.description}</p>
</div>
</div>
</div>
) : (
<>
<CardImage src={article.image} alt={article.imageAlt} />
<CardContent>
<span className='inline-block px-2 py-1 text-xs font-medium rounded-sm bg-primary-100 text-primary-800 mb-2'>
{article.category}
</span>
<h3 className='text-xl font-semibold mb-2'>{article.title}</h3>
<p className='text-gray-800 mb-4'>{article.description}</p>
<Button variant='link'>Read More</Button>
</CardContent>
</>
)}
</Card>
);
};
const ArticleContent = ({
articleContentTitle,
articleContent,
}: {
articleContentTitle: string;
articleContent: ArticleContentSection[];
}) => {
return (
<div>
<h3 className='font-medium mb-4'>{articleContentTitle}</h3>
<div className='prose max-w-none'>
{articleContent.map(section => {
if (section.type === 'heading') {
const HeadingTag =
`h${section.level}` as keyof JSX.IntrinsicElements;
const headingClass =
section.level === 1
? 'text-3xl font-bold mb-4'
: section.level === 2
? 'text-2xl font-semibold mb-3'
: 'text-xl font-semibold mb-2';
return (
<HeadingTag key={section.id} className={headingClass}>
{section.content}
</HeadingTag>
);
}
return (
<p key={section.id} className='text-gray-800 mb-4'>
{section.content}
</p>
);
})}
</div>
</div>
);
};
export { ArticleCard, ArticleContent };
export type { ArticleCardItem, ArticleContentSection };