Density and Scopes
Change the density or the theme of one part of a page
A theme sets its theme variables on :root, for the whole page. Every theme variable also works on a single element: everything inside it re-derives from the new value, while the rest of the page keeps the theme. Use this for a dense table on an otherwise roomy page, or a branded panel with its own colours.
Density levels
Add data-sa-density to any element to change the spacing and control sizes inside it. compact sets the density to 0.8 and comfortable to 1.2; text sizes stay the same.
<div class="grid w-full items-start gap-6 sm:grid-cols-3">
<sa-card data-sa-density="compact">
<sa-card-header>
<sa-card-title>Lisbon city break</sa-card-title>
<sa-card-description>data-sa-density="compact"</sa-card-description>
</sa-card-header>
<sa-card-content>
<sa-field>
<sa-field-label for="lead-compact">Lead traveller</sa-field-label>
<sa-input id="lead-compact" value="Maria Santos"/>
</sa-field>
</sa-card-content>
<sa-card-footer class="flex-wrap gap-2">
<sa-button variant="ButtonVariant.Outline">Save draft</sa-button>
<sa-button>Book trip</sa-button>
</sa-card-footer>
</sa-card>
<sa-card>
<sa-card-header>
<sa-card-title>Lisbon city break</sa-card-title>
<sa-card-description>Default density</sa-card-description>
</sa-card-header>
<sa-card-content>
<sa-field>
<sa-field-label for="lead-default">Lead traveller</sa-field-label>
<sa-input id="lead-default" value="Maria Santos"/>
</sa-field>
</sa-card-content>
<sa-card-footer class="flex-wrap gap-2">
<sa-button variant="ButtonVariant.Outline">Save draft</sa-button>
<sa-button>Book trip</sa-button>
</sa-card-footer>
</sa-card>
<sa-card data-sa-density="comfortable">
<sa-card-header>
<sa-card-title>Lisbon city break</sa-card-title>
<sa-card-description>data-sa-density="comfortable"</sa-card-description>
</sa-card-header>
<sa-card-content>
<sa-field>
<sa-field-label for="lead-comfortable">Lead traveller</sa-field-label>
<sa-input id="lead-comfortable" value="Maria Santos"/>
</sa-field>
</sa-card-content>
<sa-card-footer class="flex-wrap gap-2">
<sa-button variant="ButtonVariant.Outline">Save draft</sa-button>
<sa-button>Book trip</sa-button>
</sa-card-footer>
</sa-card>
</div>A compact section
Density is most useful on part of a page. Here the search bar keeps the page's density, and the bookings table inside <section data-sa-density="compact"> fits more rows in the same space.
<div class="flex w-full flex-col gap-6">
<div class="flex flex-wrap items-end gap-3">
<sa-field class="w-64">
<sa-field-label for="booking-search">Search bookings</sa-field-label>
<sa-input id="booking-search" placeholder="Destination or booking number"/>
</sa-field>
<sa-button>
<sa-icon name="search"/>
Search
</sa-button>
</div>
<section data-sa-density="compact">
<sa-table>
<sa-table-header>
<sa-table-row>
<sa-table-head>Booking #</sa-table-head>
<sa-table-head>Status</sa-table-head>
<sa-table-head>Destination</sa-table-head>
<sa-table-head class="text-right">Amount</sa-table-head>
</sa-table-row>
</sa-table-header>
<sa-table-body>
@foreach (var booking in StaticData.Bookings)
{
<sa-table-row>
<sa-table-cell class="font-medium">@booking.Id</sa-table-cell>
<sa-table-cell>
<sa-badge variant="@(booking.Status == BookingStatus.Cancelled ? BadgeVariant.Destructive : BadgeVariant.Secondary)">
@booking.Status.ToString()
</sa-badge>
</sa-table-cell>
<sa-table-cell>@booking.Destination</sa-table-cell>
<sa-table-cell class="text-right">@booking.Amount.ToString("N2")</sa-table-cell>
</sa-table-row>
}
</sa-table-body>
</sa-table>
</section>
</div>Any density
For a density other than the two named levels, mark the element with data-sa-scope and set --sa-density on it. The theme builder's range, 0.7 to 1.4, is a good guide.
<div class="flex w-full flex-col gap-6">
@foreach (var density in new[] { "0.7", "1", "1.4" })
{
<div data-sa-scope style="--sa-density: @density;" class="flex flex-wrap items-center gap-2">
<sa-badge variant="BadgeVariant.Outline">--sa-density: @density</sa-badge>
<sa-input class="w-56" placeholder="Where to next?"/>
<sa-button variant="ButtonVariant.Outline">Flexible dates</sa-button>
<sa-button>Search trips</sa-button>
</div>
}
</div>Branded panels
data-sa-scope marks an element that sets its own theme variables. Set any of them on the element, inline or from your own CSS, and everything inside it follows: the accent, the greys, the corners, the density. Here the rewards card has a green accent, green-tinted greys, pill controls and pill tags, next to an ordinary card.
<div class="grid w-full items-start gap-6 sm:grid-cols-2">
<sa-card>
<sa-card-header>
<sa-card-title>Your next trip</sa-card-title>
<sa-card-description>Kyoto, Japan · 12–19 April</sa-card-description>
</sa-card-header>
<sa-card-content class="flex flex-wrap gap-2">
<sa-badge>Confirmed</sa-badge>
<sa-badge variant="BadgeVariant.Secondary">2 travellers</sa-badge>
</sa-card-content>
<sa-card-footer class="gap-2">
<sa-button variant="ButtonVariant.Outline">View itinerary</sa-button>
<sa-button>Check in</sa-button>
</sa-card-footer>
</sa-card>
<sa-card data-sa-scope
style="--sa-accent: oklch(0.55 0.15 150); --sa-neutral-hue: 150; --sa-neutral-chroma: 0.02; --sa-radius: 9999px; --sa-pills: 1;">
<sa-card-header>
<sa-card-title>Voyager Rewards</sa-card-title>
<sa-card-description>You have 12,400 points to spend on your next adventure.</sa-card-description>
</sa-card-header>
<sa-card-content class="flex flex-wrap gap-2">
<sa-badge>Gold member</sa-badge>
<sa-badge variant="BadgeVariant.Secondary">2,600 points to Platinum</sa-badge>
</sa-card-content>
<sa-card-footer class="gap-2">
<sa-button variant="ButtonVariant.Outline">How it works</sa-button>
<sa-button>Redeem points</sa-button>
</sa-card-footer>
</sa-card>
</div>Contrast still follows the formulas inside a scope: text on the green accent is picked automatically, as it is for the page's own accent.
A scope's values are absolute. A scope that sets --sa-density: 1, or a data-sa-density level, replaces the theme's density inside it rather than scaling it.