Job Card
The canonical open-role block: a category badge, the role title, a one-line summary and a meta tier of location, employment type and salary band. Renders as a chrome-less row or a bordered card.
Installation#
Usage#
Compose the parts you need and omit the rest: there is no showX prop anywhere on this component. A role with no summary simply leaves out JobCardDescription; a board that links whole rows instead of showing a button leaves out JobCardAction. The eyebrow is an ordinary Badge dropped as the first child of JobCardContent, so it takes any tone in the Badge scale.
Variants#
plain (the default) is chrome-less: the row sits flush on whatever holds it, and the list that owns it paints the hairline between rows. card gives the role a box of its own: the boundary and nothing else, drawn straight on the page ground with no fill behind it, and with an inset ring rather than a border so the corner stays concentric with the radius inside it.
Layouts#
split (the default) rides the action against the right edge, vertically centred. stacked drops it below the content, which is what a card in a narrow column wants. inline is the table shape: title and meta share one line, and the meta becomes equal-width auto columns so the same template repeats on every row and the columns line up down the whole board. Both row layouts start as a stacked column and only become a row at their breakpoint (smfor split, md for inline), so an action never strands itself in the corner of a narrow frame.
Divided rows#
divided turns a stack of plain rows into a board: a hairline under each one and the vertical band that gives the row its height. It is a rule of its own rather than a border-b, so it stays straight even under a rounded interactive row. Compose it as a real list, one <li> per role and no gap between them: the hairline is the separation, and the trailing rule is dropped from the last list item so the board does not close with a line under nothing.
Interactive rows#
Pass interactive (with asChild and an <a> child) when the whole row opens the role. A plain interactive row pulls its hover fill back into a detached rounded panel so the wash does not bleed to the container edge, while the text stays flush-aligned, and the hairline pulls in by the same amount so it never overhangs the text. There is no press scale: a row that shrinks pulls its fill off the edges it sits against and starts reading as a floating tile, so the press is a deepening fill instead.
Any action, not just a button#
JobCardAction is a plain slot, so a board that wants to show who is hiring drops an AvatarGroup into it instead of a button. Nothing about the recipe changes.
API reference#
JobCard
The root <div>. Forwards all native <div> props.
variant:"plain"(default) |"card".layout:"split"(default) |"stacked"|"inline".divided:boolean; the hairline rule under the row, plus the vertical band that gives the row its height. A divided list has no gap between rows by definition, so the row supplies its own. Expects a list-item wrapper: the trailing rule is dropped from the last<li>, not from the card itself.interactive:boolean; hover/press/focus affordance for a row that is itself a link.asChild:boolean; render via Radix Slot, e.g. as an<li>or<a>.
JobCardMetaItem
One meta fact. Forwards native <span> props.
icon:ReactNode; the leading 20px glyph. A node rather than a component type so one item can pass a Phosphor icon and the next aJobCardFlag.
JobCardFlag
A country flag cropped into a ringed circle, sized to the meta glyph and marked decorative (the country is already spelled out beside it).
country:string; an ISO 3166-1 alpha-2 code, matching theiso2field of the shared countries dataset.
JobCardContent, JobCardTitle, JobCardDescription, JobCardMeta, JobCardAction
The block parts: the text column, the role title (a <div> by default, with asChild for a real heading), the muted summary, the meta tier, and the trailing action slot. Each forwards its native element props, and each is optional.