UIDex

Card

البطاقة

<article>

also calledcontent card، product card، media card، tile، teaser، preview box، كارد، كرت، بطاقة محتوى، بطاقة منتج، صندوق المحتوى، بطاقة معاينة

A card is a container that gathers everything about one entity, an article or a product, into a unit that reads on its own and repeats dozens of times across a grid. It usually holds an image, a title, a short paragraph, a metadata line, and a row of actions, and none of those parts is mandatory. A card is not a page section: a section is one division of the page structure, it appears once, and once it carries a name it joins the landmark list a screen reader offers, while a card is a repeated content unit with no business in that list. Nor is it a table row, because a row only makes sense under its column header, whereas a card carries its labels inside it and stays readable when you cut it out and paste it somewhere else. A panel is different again: a panel shows part of the current page and stays where it is, while a card stands in for a separate entity that usually has a page of its own. The practical test in review: if the box has no title of its own and no separate entity behind it, then it is a box with a border.

If you called it…

"the box with a picture and a title""the clickable rectangle in the grid""the product tile on the shop page""the preview block that opens the article""the little panel with buttons at the bottom"

Live specimen

Interact with the demo. Every part is real and numbered.

  • جديد

    مقارنة عملية بين خطوط الواجهات العربية، وأثر كلّ خطّ على أطوال السطور وعلى زمن التحميل في الصفحات الطويلة.

    ٧ دقائق قراءة

اضغط أي مكان فارغ في البطاقة ليفتح العنوان

RTLالصورة والنصّ يتبادلان بترتيب DOM وحده، بلا row-reverse

Anatomy: every part, named

Hover a row to locate it in the demo above.

Build prompt

Build a content card meant to repeat in a grid. Make the root an <article> carrying aria-labelledby that points at the heading id, and do not use <section>, which would crowd the landmark list. Order the parts: an image with width and height attributes plus aspect-ratio and an empty alt when it repeats the title, then a heading at the level the page outline calls for, then body text clamped with -webkit-line-clamp and overflow: hidden rather than a fixed height, then a metadata row holding a <time datetime> in ISO form, then an action row. Make the whole card clickable through one real link on the title carrying ::after { content: ""; position: absolute; inset: 0; z-index: 1 } with position: relative on the <article>, and never wrap the entire card in an <a>: a second <a> inside it is closed out by the HTML parser and reparented as a sibling, so the DOM stops matching your source, while a button inside it stays put but is invalid interactive nesting whose press also activates the link. Raise every other interactive element above that layer with position: relative and a higher z-index, and give each button a name that identifies its card. Put the cards in a <ul> with one <li> each, and add role="list" because list-style: none drops the list semantics in Safari with VoiceOver. Show the focus state with :focus-within on the container instead of :hover alone. Write every space and position with logical properties only: padding-inline, margin-inline-start, inset-inline-end, border-inline-start, border-start-start-radius. No left, no right, and no row-reverse under [dir="rtl"].

How it behaves right-to-left

This section is ours alone.

The horizontal card

mirrors

In a horizontal card the media sits at the logical start with the text after it, and the swap happens from DOM order alone, because the inline axis of flex and grid follows direction: the first element in source order lands on the right in Arabic without a single line of CSS, and grid-template-columns: 96px 1fr puts the media column on the right by itself. Do not add flex-direction: row-reverse under [dir="rtl"]: that is a second flip, it sends the media back to the wrong side, and it leaves the visual order disagreeing with Tab order.

Padding and corner radius

mirrors

Every asymmetric space inside the card is written logically: padding-inline for the side padding, and margin-inline-end on the media for the gap that separates it from the text. Media that runs flush to the edge of a horizontal card is rounded on the two inline-start corners, border-start-start-radius and border-end-start-radius, so both travel with direction, while border-top-left-radius stays on the left and cuts the image on the wrong side after the flip. The coloured stripe that marks the card type is drawn with border-inline-start: 3px solid rather than border-left.

The action row

mirrors

The primary action stands at the logical end of the row, which in Arabic is the left side of the card. Put it last in the DOM and use justify-content: flex-end on the row, or margin-inline-start: auto on the first button in the group, since flex-end measures from the end of an inline axis that already flipped with the page. Never margin-left: auto, which is physical, and do not reorder with the order property, which moves what the eye sees and leaves Tab order where it was.

The corner badge

mirrors

A badge pinned to the card corner ("New", "20% off") travels with direction, so anchor it with inset-inline-end: 8px instead of right: 8px. Do not mirror it with transform: scaleX(-1), which flips its text and its digits along with the box and leaves both readable only in a mirror. A diagonal ribbon needs one extra step, because rotate is a physical property with no sense of direction: write rotate(calc(45deg * var(--dir))) and set --dir to -1 under [dir="rtl"], or the ribbon leans the opposite way and slides off the corner.

The image itself

never mirrors

The content of an image never mirrors. A blanket [dir="rtl"] img { transform: scaleX(-1) } flips screenshots, logos, and faces, and every word inside the picture comes out backwards. Cropping needs the same care: object-position takes a position value with no logical keyword in it, only center, left, right, top, bottom, lengths and percentages, so a thumbnail cropped with object-position: left center keeps cropping from the physical left after the flip, and you have to swap it yourself under [dir="rtl"] when the subject of the photo demands it.

In code

Each row is one framework's word for the same thing. Take the row your project speaks.

HTML<article>The natural card root; its implicit role is article and not a landmark, so it stays out of the landmark list.
CSS::after { position: absolute; inset: 0 }The stretched hit layer that turns the whole card into one target from a single link.
ARIAaria-labelledby="card-title"Names the <article> from its own heading, so it is announced by title when jumping between articles.
shadcn/ui<Card><CardHeader><CardTitle>Card and CardTitle both render a styled <div>, so the semantic element, the heading level, and the stretched link are yours to add.
Material UI<CardActionArea>Renders a button wrapped around the card content, so a link or button inside it is invalid nested interactive content.
Ant Design<Card cover={…} actions={[…]}>actions renders as a <ul> in the footer, and each action still needs a name that identifies its card.
CSSborder-start-start-radiusThe logical corner for media flush with the edge of a horizontal card; it travels with direction on its own.

See also

Updated · 2026-08-19