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.

On this page