Patterns

Patterns

Common layouts built only from Aster components. Copy the code as a starting point, then connect it to your own data and logic.

Sign-in form

Keep it short, with visible labels. The primary button comes first, and the password help is a link because it leads somewhere else.

Sign in

Use your work email and password.

HTML
<form class="as-card" style="max-width: 400px;">
  <header class="as-card__header">
    <h3 class="as-card__title">Sign in</h3>
    <p class="as-card__description">Use your work email and password.</p>
  </header>
  <div class="as-form-field">
    <label class="as-form-field__label" for="login-email">Email</label>
    <div class="as-input-box">
      <input class="as-input" type="email" id="login-email" autocomplete="email" required>
    </div>
  </div>
  <div class="as-form-field">
    <label class="as-form-field__label" for="login-password">Password</label>
    <div class="as-input-box">
      <input class="as-input" type="password" id="login-password" autocomplete="current-password" required>
    </div>
  </div>
  <label class="as-checkbox-field">
    <span class="as-checkbox"><input type="checkbox" class="as-checkbox-input"></span>
    <span class="as-checkbox-field__content"><span class="as-checkbox-field__label">Keep me signed in</span></span>
  </label>
  <footer class="as-card__footer">
    <button type="submit" class="as-button as-button--primary"><span class="as-button__label">Sign in</span></button>
    <a class="as-link" href="#sign-in">Forgot your password?</a>
  </footer>
</form>

Settings

Use switches when a change applies immediately. Each one names the setting and says what it does.

Notifications

These settings apply immediately.

HTML
<section class="as-card" style="max-width: 560px;">
  <header class="as-card__header">
    <h3 class="as-card__title">Notifications</h3>
    <p class="as-card__description">These settings apply immediately.</p>
  </header>
  <label class="as-switch-field">
    <span class="as-switch">
      <input type="checkbox" role="switch" class="as-switch-input" checked>
      <span class="as-switch-track"><span class="as-switch-thumb"></span></span>
    </span>
    <span class="as-switch-field__content">
      <span class="as-switch-field__label">Deployment alerts</span>
      <span class="as-switch-field__description">Tell me when a deployment fails.</span>
    </span>
  </label>
  <label class="as-switch-field">
    <span class="as-switch">
      <input type="checkbox" role="switch" class="as-switch-input">
      <span class="as-switch-track"><span class="as-switch-thumb"></span></span>
    </span>
    <span class="as-switch-field__content">
      <span class="as-switch-field__label">Weekly summary</span>
      <span class="as-switch-field__description">Email me a report every Monday.</span>
    </span>
  </label>
</section>

Table with filters

Put filters directly above the data they change, and align the fields along their bottom edge. Connecting the filters to your data is up to your app.

Cluster nodes
Node Status Latency
iad-01 Active 1.24 ms
gru-05 Offline 38.70 ms
HTML
<div style="display: flex; flex-direction: column; gap: var(--as-space-4); width: 100%;">
  <div style="display: flex; flex-wrap: wrap; align-items: flex-end; gap: var(--as-space-3);">
    <div class="as-form-field" style="flex: 1 1 220px; margin: 0;">
      <label class="as-form-field__label" for="filter-search">Search nodes</label>
      <div class="as-input-box"><input class="as-input" type="search" id="filter-search"></div>
    </div>
    <div class="as-form-field" style="flex: 0 1 180px; margin: 0;">
      <label class="as-form-field__label" for="filter-status">Status</label>
      <div class="as-select-box">
        <select class="as-select" id="filter-status">
          <option>All</option>
          <option>Active</option>
          <option>Offline</option>
        </select>
      </div>
    </div>
  </div>
  <div class="as-table-container">
    <div class="as-table-scroll">
      <table class="as-table as-table--interactive">
        <caption class="as-sr-only">Cluster nodes</caption>
        <thead>
          <tr>
            <th scope="col" class="as-table__th">Node</th>
            <th scope="col" class="as-table__th">Status</th>
            <th scope="col" class="as-table__th as-table__th--num">Latency</th>
          </tr>
        </thead>
        <tbody>
          <tr class="as-table__row">
            <th scope="row" class="as-table__cell as-table__cell--primary">iad-01</th>
            <td class="as-table__cell"><span class="as-badge as-badge--success">Active</span></td>
            <td class="as-table__cell as-table__cell--num">1.24 ms</td>
          </tr>
          <tr class="as-table__row">
            <th scope="row" class="as-table__cell as-table__cell--primary">gru-05</th>
            <td class="as-table__cell"><span class="as-badge as-badge--critical">Offline</span></td>
            <td class="as-table__cell as-table__cell--num">38.70 ms</td>
          </tr>
        </tbody>
      </table>
    </div>
  </div>
</div>

Empty, error, and loading

Every data view needs these three. An empty state offers a next step, an error says how to recover, and a skeleton matches the shape of what is loading.

No projects yet

Create your first project to start tracking deployments.

HTML
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); gap: var(--as-space-4); width: 100%;">
  <section class="as-card">
    <h3 class="as-card__title">No projects yet</h3>
    <p class="as-card__description">Create your first project to start tracking deployments.</p>
    <footer class="as-card__footer">
      <button type="button" class="as-button as-button--primary"><span class="as-button__label">Create project</span></button>
    </footer>
  </section>
  <div class="as-alert as-alert--critical" role="alert" style="align-self: start;">
    <div class="as-alert__content">
      <div class="as-alert__title">Couldn't load projects</div>
      <div class="as-alert__description">Check your connection, then reload the page.</div>
    </div>
  </div>
  <div aria-busy="true" style="display: flex; gap: var(--as-space-3); align-self: start;">
    <div class="as-skeleton as-skeleton--avatar" aria-hidden="true" style="width: 40px; height: 40px; flex: none;"></div>
    <div style="flex: 1; display: flex; flex-direction: column; gap: var(--as-space-2);">
      <div class="as-skeleton as-skeleton--text" aria-hidden="true" style="width: 100%;"></div>
      <div class="as-skeleton as-skeleton--text" aria-hidden="true" style="width: 60%;"></div>
    </div>
  </div>
</div>
Esc
↑↓ navigateEnter openEsc close