Skip to content
ShineUI
Install

Card

The standard surface.

<sui-card>PanelsStable

About

The standard surface: the surface token, one hairline border, 12px corners, no resting shadow. The host element is the card itself, so size and position it directly. Use it for any block of related content that needs an edge, from a summary tile to a form.

Usage

<sui-card heading="A/R aging" subtitle="As of Aug 22, 2026"> <div style="display:flex;justify-content:space-between;gap:16px;padding:6px 0;border-bottom:1px solid var(--hairline)"><span style="font-size:13px;color:var(--ink-muted)">Current</span><span style="font-size:13px;font-feature-settings:'tnum'">$47,630.00</span></div> <div style="display:flex;justify-content:space-between;gap:16px;padding:6px 0;border-bottom:1px solid var(--hairline)"><span style="font-size:13px;color:var(--ink-muted)">1 to 30 days</span><span style="font-size:13px;font-feature-settings:'tnum'">$28,415.00</span></div> <div style="display:flex;justify-content:space-between;gap:16px;padding:6px 0;border-bottom:1px solid var(--hairline)"><span style="font-size:13px;color:var(--ink-muted)">31 to 60 days</span><span style="font-size:13px;font-feature-settings:'tnum'">$9,240.00</span></div> <div style="display:flex;justify-content:space-between;gap:16px;padding:6px 0;border-bottom:1px solid var(--hairline)"><span style="font-size:13px;color:var(--ink-muted)">Over 60 days</span><span style="font-size:13px;font-feature-settings:'tnum'">$5,455.00</span></div> </sui-card> <script type="module"> import { defineCustomElements } from "@shineui/core/loader"; import "@shineui/core/shineui.css"; defineCustomElements(); </script>
import { SuiCard } from "@shineui/react"; import "@shineui/core/shineui.css"; export function Example() { return ( <SuiCard heading="A/R aging" subtitle="As of Aug 22, 2026"> <div style="display:flex;justify-content:space-between;gap:16px;padding:6px 0;border-bottom:1px solid var(--hairline)"><span style="font-size:13px;color:var(--ink-muted)">Current</span><span style="font-size:13px;font-feature-settings:'tnum'">$47,630.00</span></div> <div style="display:flex;justify-content:space-between;gap:16px;padding:6px 0;border-bottom:1px solid var(--hairline)"><span style="font-size:13px;color:var(--ink-muted)">1 to 30 days</span><span style="font-size:13px;font-feature-settings:'tnum'">$28,415.00</span></div> <div style="display:flex;justify-content:space-between;gap:16px;padding:6px 0;border-bottom:1px solid var(--hairline)"><span style="font-size:13px;color:var(--ink-muted)">31 to 60 days</span><span style="font-size:13px;font-feature-settings:'tnum'">$9,240.00</span></div> <div style="display:flex;justify-content:space-between;gap:16px;padding:6px 0;border-bottom:1px solid var(--hairline)"><span style="font-size:13px;color:var(--ink-muted)">Over 60 days</span><span style="font-size:13px;font-feature-settings:'tnum'">$5,455.00</span></div> </SuiCard> ); }
<script setup> import { SuiCard } from "@shineui/vue"; import "@shineui/core/shineui.css"; </script> <template> <SuiCard heading="A/R aging" subtitle="As of Aug 22, 2026"><div style="display:flex;justify-content:space-between;gap:16px;padding:6px 0;border-bottom:1px solid var(--hairline)"><span style="font-size:13px;color:var(--ink-muted)">Current</span><span style="font-size:13px;font-feature-settings:'tnum'">$47,630.00</span></div> <div style="display:flex;justify-content:space-between;gap:16px;padding:6px 0;border-bottom:1px solid var(--hairline)"><span style="font-size:13px;color:var(--ink-muted)">1 to 30 days</span><span style="font-size:13px;font-feature-settings:'tnum'">$28,415.00</span></div> <div style="display:flex;justify-content:space-between;gap:16px;padding:6px 0;border-bottom:1px solid var(--hairline)"><span style="font-size:13px;color:var(--ink-muted)">31 to 60 days</span><span style="font-size:13px;font-feature-settings:'tnum'">$9,240.00</span></div> <div style="display:flex;justify-content:space-between;gap:16px;padding:6px 0;border-bottom:1px solid var(--hairline)"><span style="font-size:13px;color:var(--ink-muted)">Over 60 days</span><span style="font-size:13px;font-feature-settings:'tnum'">$5,455.00</span></div></SuiCard> </template>
import { Component } from "@angular/core"; import { SuiCard } from "@shineui/angular"; @Component({ standalone: true, imports: [SuiCard], template: `<sui-card heading="A/R aging" subtitle="As of Aug 22, 2026"><div style="display:flex;justify-content:space-between;gap:16px;padding:6px 0;border-bottom:1px solid var(--hairline)"><span style="font-size:13px;color:var(--ink-muted)">Current</span><span style="font-size:13px;font-feature-settings:'tnum'">$47,630.00</span></div> <div style="display:flex;justify-content:space-between;gap:16px;padding:6px 0;border-bottom:1px solid var(--hairline)"><span style="font-size:13px;color:var(--ink-muted)">1 to 30 days</span><span style="font-size:13px;font-feature-settings:'tnum'">$28,415.00</span></div> <div style="display:flex;justify-content:space-between;gap:16px;padding:6px 0;border-bottom:1px solid var(--hairline)"><span style="font-size:13px;color:var(--ink-muted)">31 to 60 days</span><span style="font-size:13px;font-feature-settings:'tnum'">$9,240.00</span></div> <div style="display:flex;justify-content:space-between;gap:16px;padding:6px 0;border-bottom:1px solid var(--hairline)"><span style="font-size:13px;color:var(--ink-muted)">Over 60 days</span><span style="font-size:13px;font-feature-settings:'tnum'">$5,455.00</span></div></sui-card>`, }) export class Example { }

Basic

The header renders only when heading or header-end content exists, and the footer only when footer content does. The card emits nothing; it is a surface.

Current$47,630.00
1 to 30 days$28,415.00
31 to 60 days$9,240.00
Over 60 days$5,455.00
<sui-card heading="A/R aging" subtitle="As of Aug 22, 2026" style="max-width:360px"> <div style="display:flex;justify-content:space-between;gap:16px;padding:6px 0;border-bottom:1px solid var(--hairline)"><span style="font-size:13px;color:var(--ink-muted)">Current</span><span style="font-size:13px;font-feature-settings:'tnum'">$47,630.00</span></div> <div style="display:flex;justify-content:space-between;gap:16px;padding:6px 0;border-bottom:1px solid var(--hairline)"><span style="font-size:13px;color:var(--ink-muted)">1 to 30 days</span><span style="font-size:13px;font-feature-settings:'tnum'">$28,415.00</span></div> <div style="display:flex;justify-content:space-between;gap:16px;padding:6px 0;border-bottom:1px solid var(--hairline)"><span style="font-size:13px;color:var(--ink-muted)">31 to 60 days</span><span style="font-size:13px;font-feature-settings:'tnum'">$9,240.00</span></div> <div style="display:flex;justify-content:space-between;gap:16px;padding:6px 0;border-bottom:1px solid var(--hairline)"><span style="font-size:13px;color:var(--ink-muted)">Over 60 days</span><span style="font-size:13px;font-feature-settings:'tnum'">$5,455.00</span></div> </sui-card>

header-end takes a link or one small action at the end of the heading row. The footer sits under a hairline and carries meta text.

Current$47,630.00
1 to 30 days$28,415.00
31 to 60 days$9,240.00
Over 60 days$5,455.00
Updated 24 minutes ago
<sui-card heading="A/R aging" subtitle="As of Aug 22, 2026" style="max-width:360px"> <sui-button slot="header-end" variant="link" size="sm" label="Review"></sui-button> <div style="display:flex;justify-content:space-between;gap:16px;padding:6px 0;border-bottom:1px solid var(--hairline)"><span style="font-size:13px;color:var(--ink-muted)">Current</span><span style="font-size:13px;font-feature-settings:'tnum'">$47,630.00</span></div> <div style="display:flex;justify-content:space-between;gap:16px;padding:6px 0;border-bottom:1px solid var(--hairline)"><span style="font-size:13px;color:var(--ink-muted)">1 to 30 days</span><span style="font-size:13px;font-feature-settings:'tnum'">$28,415.00</span></div> <div style="display:flex;justify-content:space-between;gap:16px;padding:6px 0;border-bottom:1px solid var(--hairline)"><span style="font-size:13px;color:var(--ink-muted)">31 to 60 days</span><span style="font-size:13px;font-feature-settings:'tnum'">$9,240.00</span></div> <div style="display:flex;justify-content:space-between;gap:16px;padding:6px 0;border-bottom:1px solid var(--hairline)"><span style="font-size:13px;color:var(--ink-muted)">Over 60 days</span><span style="font-size:13px;font-feature-settings:'tnum'">$5,455.00</span></div> <span slot="footer">Updated 24 minutes ago</span> </sui-card>

Hoverable

hoverable lifts the card on hover and while anything inside it holds focus. It adds no role and no tab stop, so the action inside has to be a real link or button.

Open the invoice Resting card. No lift, no pointer affordance.
<div style="display:grid;grid-template-columns:1fr 1fr;gap:14px"> <sui-card hoverable heading="Silver Hills Cafe" subtitle="INV-1043, due Sep 5"> <a href="#card-hoverable" style="font-size:13px;font-weight:600;color:var(--link)">Open the invoice</a> </sui-card> <sui-card heading="Falls City Supply" subtitle="INV-1044, due Sep 12"> <span style="font-size:13px;color:var(--ink-muted)">Resting card. No lift, no pointer affordance.</span> </sui-card> </div>

Body without padding

flush lets a list or a table run to the card edges and hands it the card's 20px inset, --sui-card-pad-x, so a sui-list's rows start on the heading's line. A link button in header-end has no side padding, so View all ends on the content line. Set --sui-card-pad-x on the card to move every edge.

<sui-card heading="Recent deposits" flush style="max-width:360px"> <sui-button slot="header-end" variant="link" size="sm" label="View all"></sui-button> <sui-list id="card-deposits" aria-label="Recent deposits"></sui-list> </sui-card>
// setup(root): properties and events setup(root) { root.querySelector('#card-deposits').items = [ { id: 'dep-1', title: 'First Harrison', description: 'Aug 22, 2026', meta: '$12,400.00' }, { id: 'dep-2', title: 'First Harrison', description: 'Aug 19, 2026', meta: '$3,180.00' }, { id: 'dep-3', title: 'Payroll 4410', description: 'Aug 15, 2026', meta: '$8,905.00' }, ]; }

With a form

The card is not form associated. Put the form inside it, and use the footer for the actions.

Send to see the collected values.
<sui-card heading="Send a reminder" subtitle="INV-1043, Silver Hills Cafe" style="max-width:360px"> <form id="card-form" style="display:flex;flex-direction:column;gap:12px"> <sui-input-text size="md" name="to" label="To" value="owner@silverhillscafe.com"></sui-input-text> <sui-textarea rows="2" name="note" label="Note" value="A friendly nudge: INV-1043 is due Sep 5."></sui-textarea> <output id="card-out" style="font:460 13px/1.6 var(--font-sans);color:var(--ink-muted)">Send to see the collected values.</output> </form> <div slot="footer" style="display:flex;gap:8px"> <sui-button id="card-send" variant="brand" size="sm" label="Send reminder"></sui-button> <sui-button id="card-reset" variant="ghost" size="sm" label="Reset"></sui-button> </div> </sui-card>
// setup(root): properties and events setup(root) { const form = root.querySelector('#card-form'); const out = root.querySelector('#card-out'); root.querySelector('#card-send').addEventListener('click', () => { out.textContent = [...new FormData(form)].map(([k, v]) => `${k}=${v || '(empty)'}`).join(' '); }); root.querySelector('#card-reset').addEventListener('click', () => { form.reset(); out.textContent = 'Reset.'; }); }

API

<sui-card>

Properties

PropertyAttributeTypeDefaultDescription
flushflushbooleanfalseTakes the body's padding off (it is 18/20px, 12/20/18px under a header), for tables and lists: the body then hands the card's 20px inset (--sui-card-pad-x) to what it holds, so a sui-list's rows and a sui-data-table's first and last columns and bars start on the heading's line. Set --sui-card-pad-x on the card to move every edge at once. Reflected.
headingheadingstringstring | undefinedCard title (the reference title prop; renamed for the native attribute). The header renders only when a heading or header-end content exists.
hoverablehoverablebooleanfalseLifts the card (--shadow-raised) and sharpens its border on hover, and while anything inside it holds focus. Only for clickable cards.
subtitlesubtitlestringstring | undefinedSecondary line under the heading, in faint ink.

Slots

SlotDescription
(default)Card body.
footerMeta line in the hairline-topped footer. The footer renders only when this slot has content.
header-endContent at the end of the header row, after the titles (a link or a small action). The header renders when this slot or heading has content.

Theming

Every design token this element can render with, read from its own stylesheets and the ones it imports. Set a token on :root or on any ancestor and every use below follows it. The values shown are the ones in the theme you are viewing. The list is read per stylesheet, so an element that imports a shared drawing module lists that module's tokens too, whether or not it paints with all of them.

See also