Getting Started
Components
- Advanced Toast
- ArrowButton
- Arrows
- Avatar
- Backdrop
- Basic Toast
- Breadcrumb
- Button
- CardBackground
- Card
- Carousel
- ChatButton
- ChatInput
- ChatMessage
- ChatTypingIndicator
- ChatWindow
- CheckboxSelect
- Checkbox
- Checkboxes
- Chip
- ChoiceExpander
- Clickable
- Date Field
- Dropdown
- ElasticHeight
- ExpandableCard
- Filter
- Form Control
- Form Label
- Frame
- HamburgerIcon
- Heading
- Image
- Link
- Loader
- Number Field
- Pagination
- Password
- Phone Field
- Popin
- Portal
- Prose
- Radio Group
- Radio
- Range
- Scrollbar
- Select
- SidebarLayout
- SliderFrame
- Spinner
- Switch
- Tabs
- Tag
- TextField
- Tooltip
- ValidationMessage
'use client';
import { Frame } from '@/ui/frame/Frame';
import { FrameLabelsWrapper } from '@/ui/frame/FrameLabelsWrapper';
import { FrameTitle } from '@/ui/frame/FrameTitle';
export function FrameDemo() {
return (
<Frame
format="horizontal"
className="max-w-380"
image={{
alt: 'Mountain landscape',
src: 'https://images.unsplash.com/photo-1506905925346-21bda4d32df4',
}}
>
<FrameLabelsWrapper hasImage>
<FrameTitle className="w-full justify-center text-center" coverLink hasImage href="#">
<div>
<div className="text-b2 font-semibold">Mountain Resort</div>
<div className="text-b3">Winter</div>
</div>
</FrameTitle>
</FrameLabelsWrapper>
</Frame>
);
}
Overview
Frame is a low-level primitive that renders an image inside an aspect-ratio container with rounded corners and overflow clipping. It is designed to be composed with FrameLabelsWrapper and FrameTitle to build image cards with overlaid content.
When to use Frame:
- You need an image card with custom overlaid content (tags, titles, buttons)
- You want full control over the layout inside the frame
- You need to combine multiple overlay elements
Related components:
FrameWrapper— the base container without image logic, for custom contentFrameLabelsWrapper— overlay container with optional gradientFrameTitle— clickable/static title bar inside a frameSliderFrame— multi-image carousel variant
Installation
npx shadcn@latest add https://develop.trident-ui.pro.clubmed/r/frame.json
Usage
import { Frame } from '@/ui/frame/Frame';
import { FrameLabelsWrapper } from '@/ui/frame/FrameLabelsWrapper';
import { FrameTitle } from '@/ui/frame/FrameTitle';
export function Component() {
return (
<Frame
format="horizontal"
className="max-w-380"
image={{
alt: 'Mountain landscape',
src: 'https://images.unsplash.com/photo-1506905925346-21bda4d32df4',
}}
>
<FrameLabelsWrapper hasImage>
<FrameTitle className="w-full justify-center text-center" coverLink hasImage href="#">
<div className="text-b2 font-semibold">Mountain Resort</div>
</FrameTitle>
</FrameLabelsWrapper>
</Frame>
);
}API Reference
Frame
| Prop | Type | Default | Description |
|---|---|---|---|
image | ImageProps | null | Required | Image props passed to the Image component. null renders no image |
sizes | string | undefined | Responsive image sizes attribute, overrides image.sizes |
format | "horizontal" | "vertical" | "square" | "custom" | "horizontal" | Aspect ratio of the frame |
as | "article" | "div" | "span" | "article" | HTML element to render |
className | string | undefined | Additional CSS classes |
children | ReactNode | undefined | Overlay content (use FrameLabelsWrapper + FrameTitle) |
Examples
With button
'use client';
import { Button } from '@/ui/buttons/Button';
import { Frame } from '@/ui/frame/Frame';
import { FrameLabelsWrapper } from '@/ui/frame/FrameLabelsWrapper';
import { FrameTitle } from '@/ui/frame/FrameTitle';
export function FrameHorizontalDemo() {
return (
<Frame
format="horizontal"
className="max-w-380"
image={{
alt: 'Mountain landscape',
src: 'https://images.unsplash.com/photo-1506905925346-21bda4d32df4',
}}
>
<FrameLabelsWrapper hasImage>
<FrameTitle className="w-full justify-between" coverLink hasImage href="#">
<div>
<div className="text-b2 font-semibold">Alpine Adventures</div>
<div className="text-b3">Explore</div>
</div>
<Button
aria-label="Explore Alpine Adventures"
className="pointer-events-auto shrink-0"
color="white"
icon="ArrowDefaultRight"
theme="outline"
variant="circle"
/>
</FrameTitle>
</FrameLabelsWrapper>
</Frame>
);
}
Vertical format
'use client';
import { Button } from '@/ui/buttons/Button';
import { Frame } from '@/ui/frame/Frame';
import { FrameLabelsWrapper } from '@/ui/frame/FrameLabelsWrapper';
import { FrameTitle } from '@/ui/frame/FrameTitle';
export function FrameVerticalDemo() {
return (
<Frame
format="vertical"
className="max-w-280"
image={{
alt: 'Mountain landscape',
src: 'https://images.unsplash.com/photo-1506905925346-21bda4d32df4',
}}
>
<FrameLabelsWrapper hasImage>
<FrameTitle className="w-full justify-between" coverLink hasImage href="#">
<div>
<div className="text-b2 font-semibold">Ski Resort</div>
<div className="text-b3">Alpine</div>
</div>
<Button
aria-label="Explore Ski Resort"
className="pointer-events-auto shrink-0"
color="white"
icon="ArrowDefaultRight"
theme="outline"
variant="circle"
/>
</FrameTitle>
</FrameLabelsWrapper>
</Frame>
);
}
With tag
Best seller
Mountain Resort
Winter
'use client';
import { Button } from '@/ui/buttons/Button';
import { Tag } from '@/ui/Tag';
import { Frame } from '@/ui/frame/Frame';
import { FrameLabelsWrapper } from '@/ui/frame/FrameLabelsWrapper';
import { FrameTitle } from '@/ui/frame/FrameTitle';
export function FrameWithTagDemo() {
return (
<Frame
format="horizontal"
className="max-w-380"
image={{
alt: 'Mountain landscape',
src: 'https://images.unsplash.com/photo-1506905925346-21bda4d32df4',
}}
>
<FrameLabelsWrapper hasImage>
<Tag
label="Best seller"
className="mx-12 mt-12 self-start px-8 sm:mx-24 sm:mt-24 sm:px-16"
/>
<FrameTitle className="w-full justify-between" coverLink hasImage href="#">
<div>
<div className="text-b2 font-semibold">Mountain Resort</div>
<div className="text-b3">Winter</div>
</div>
<Button
aria-label="Explore Mountain Resort"
className="pointer-events-auto shrink-0"
color="white"
icon="ArrowDefaultRight"
theme="outline"
variant="circle"
/>
</FrameTitle>
</FrameLabelsWrapper>
</Frame>
);
}
Without image
Partnership
Special Offer
Explore
'use client';
import { Button } from '@/ui/buttons/Button';
import { Tag } from '@/ui/Tag';
import { Frame } from '@/ui/frame/Frame';
import { FrameLabelsWrapper } from '@/ui/frame/FrameLabelsWrapper';
import { FrameTitle } from '@/ui/frame/FrameTitle';
export function FrameNoImageDemo() {
return (
<Frame format="horizontal" image={null} className="bg-saffron h-240 w-380">
<FrameLabelsWrapper hasImage={false}>
<Tag
label="Partnership"
border="ultramarine"
color="white"
backgroundColor="ultramarine"
className="mx-12 mt-12 self-start px-8 sm:mx-24 sm:mt-24 sm:px-16"
/>
<FrameTitle className="w-full justify-between" coverLink href="#">
<div>
<div className="text-b2 font-semibold">Special Offer</div>
<div className="text-b3">Explore</div>
</div>
<Button
aria-label="Explore Special Offer"
className="pointer-events-auto shrink-0"
color="black"
icon="ArrowDefaultRight"
theme="outline"
variant="circle"
/>
</FrameTitle>
</FrameLabelsWrapper>
</Frame>
);
}
Notes
- Frame renders as an
<article>by default — useas="div"oras="span"for non-semantic contexts - The image fills the frame with
object-cover, preserving aspect ratio - Pass
image={null}to render the frame without a background image (e.g., with a background color viaclassName) - Overlay content should be wrapped in
FrameLabelsWrapperto handle pointer events and the gradient background