Buttons

.btn is one of the very few component primitives in Sulphuris. Most styling in Sulphuris is done with utilities; .btn exists because a button has enough stateful and interactive complexity (hover, motion, color inversion) that generating it from utilities alone would be verbose. Compose freely with utilities for anything the modifier classes don't cover.

Base: .btn

Resets <button> appearance, then applies:

Property Value (from $button) Emitted
min-height 56px 3.5rem
padding 16px 32px 1rem 2rem
border-radius 4px 0.25rem
background-color primary (#f6c026 light / #3F00FF dark)
color foreground (#1a1a1d light / #ffffff dark)
display inline-flex
justify-content center
align-items center

Hover (pointer devices only โ€” applied inside $container-breakpoint / lg min-width media query):

background-color โ†’ foreground (#1a1a1d light / #ffffff dark)
color            โ†’ background (#ffffff light / #1a1a1d dark)

The label is restated on hover because the two surfaces want opposite text. primary carries no contrast floor โ€” it is a brand colour, free to be a yellow โ€” so the only label guaranteed to be readable on it is foreground, while foreground as a fill wants background. Both pairs clear WCAG AA: 10.3:1 and 17.4:1 light, 7.9:1 and 17.4:1 dark.

Motion โ€” color and background-color transitions are added when prefers-reduced-motion: no-preference is true. No transition is applied when the user has requested reduced motion.

Note

.btn also doubles as a reset when applied to a <button> element. The same reset rules (no border, no background, no padding, color: inherit) fire on both button.reset and .btn before the component styles kick in.

Usage

<!-- On an anchor -->
<a href="/start" class="btn">Get started</a>

<!-- On a native button -->
<button type="submit" class="btn">Submit</button>

Modifier: .btn-inverted

Swaps fill to foreground with hover reverting to primary. The label swaps with it, for the same reason .btn's does.

background-color: foreground  โ†’  hover: primary
color:            background   โ†’  hover: foreground
<a href="#" class="btn btn-inverted">Inverted</a>

Modifier: .btn-outline

Transparent background with a 2px solid border. The padding is reduced by the border width so the button stays the same overall height as .btn.

Exactly so at the default root. The padding converts under $rem-units and the border width does not โ€” border widths stay px everywhere, being fuzzy at fractional sizes โ€” so at a reader font-size other than 16px the two variants differ by under a pixel.

Property Value
background transparent
border 2px solid foreground
color foreground

Hover fills with foreground and flips color to background.

<a href="#" class="btn btn-outline">Outline</a>

Combined: .btn-outline.btn-inverted

Border and text use background color. Hover fills with background and sets color to foreground.

This one only works on a dark surface โ€” it paints itself in the page's own background colour, so on the page background it is invisible rather than subtle. Put it on foreground, on a photograph, or on any fill dark enough to carry it; the sample gives it .bg-foreground for that reason.

<div class="bg-foreground p-16">
  <a href="#" class="btn btn-outline btn-inverted">Outline inverted</a>
</div>

Composing with utilities

.btn intentionally provides no typography, width, icon spacing, or shadow utilities โ€” use the Sulphuris utility set for those:

<!-- Rounded pill shape -->
<a href="#" class="btn rounded-32">Pill</a>

<!-- Full-width on mobile -->
<a href="#" class="btn d-block d-lg-inline-flex">Full width mobile</a>

<!-- Custom background via utility (overrides .btn fill) -->
<a href="#" class="btn bg-primary">Primary fill</a>

Note

Because .btn sets background-color directly, a utility like .bg-primary will override it โ€” that is intentional. Sulphuris utilities avoid !important so the cascade is your control.

Retuning via $button

Override any key in the $button map before importing Sulphuris:

// _config-overrides.scss  (loaded before @use "sulphuris")
@use "sulphuris/src/core/config" with (
  $button: (
    height: 48px,
    padding-x: 24px,
    padding-y: 12px,
    border-width: 2px,
    border-radius: 0
  )
);
Key Default Effect
height 56px min-height on .btn
padding-x 32px horizontal padding
padding-y 16px vertical padding
border-width 2px border on .btn-outline, inset padding compensation
border-radius 4px border-radius on .btn

Written in px, emitted in rem under $rem-units โ€” border-width excepted, with every other border width. The label is already rem, so the box has to convert with it or a reader at a larger font-size gets scaled text in a fixed 56px frame.