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 multiplies the theme's density by 0.8 and comfortable by 1.2, so they stay tighter or roomier than whatever theme is active; 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 replaces the theme's density inside it. A data-sa-density level is relative: it scales whatever density is in effect, including a scope's.

On this page