Contents
Styling
Carats uses scoped SASS for styling, promoting semantic HTML with minimal CSS classes.
base.sass
The base.sass file in src/client contains global design tokens and style resets. All other SASS files import it with @use and as * so the tokens are available without a namespace prefix.
Creating base.sass
// Colors
$color-primary: #000000
$color-secondary: #ffffff
$color-background: #f5f5f5
$color-text: #333333
$color-text-light: #666666
// Spacing
$spacing-xs: 4px
$spacing-sm: 8px
$spacing-md: 16px
$spacing-lg: 24px
$spacing-xl: 32px
// Typography
$font-family-base: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif
$font-size-base: 16px
$font-size-sm: 14px
$font-size-lg: 18px
$font-size-xl: 24px
$font-weight-normal: 400
$font-weight-bold: 700
// Breakpoints
$breakpoint-sm: 576px
$breakpoint-md: 768px
$breakpoint-lg: 992px
$breakpoint-xl: 1200px
// Border radius
$border-radius: 4px
$border-radius-lg: 8px
// Box shadows
$box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1)
$box-shadow-lg: 0 4px 8px rgba(0, 0, 0, 0.15)
// Transitions
$transition-fast: 150ms ease
$transition-normal: 250ms ease
Design Tokens
Define colors, spacing, typography, breakpoints and other design tokens at the top level of base.sass. Do not nest them.
Correct Usage
@use '../base.sass' as *
.my-component
color: $color-text
padding: $spacing-md
font-size: $font-size-base
Incorrect Usage
// Tokens must stay at the top level of base.sass
.my-component
$color-text: #333
.inner
color: $color-text
A token defined inside a rule only exists inside that rule, so any other stylesheet that needs it has to redefine it. This is how a palette quietly drifts apart across a project.
Semantic HTML
Prefer semantic HTML elements with minimal class usage. Let the component structure and the design tokens handle the styling, and nest selectors in SASS instead of adding utility classes to the markup.
Example Component
import './card.sass';
export default function Card(props: { title: string; children?: JSX.Element }) {
return (
<article class="card">
<header>
<h2>{props.title}</h2>
</header>
<section>
{props.children}
</section>
<footer>
<button>Action</button>
</footer>
</article>
);
}
Corresponding SASS
@use '../base.sass' as *
.card
background: $color-secondary
border-radius: $border-radius-lg
box-shadow: $box-shadow
padding: $spacing-md
margin-bottom: $spacing-md
header
margin-bottom: $spacing-sm
h2
font-size: $font-size-lg
font-weight: $font-weight-bold
section
margin-bottom: $spacing-md
footer
display: flex
justify-content: flex-end
button
background: $color-primary
color: $color-secondary
padding: $spacing-sm $spacing-md
border: none
border-radius: $border-radius
cursor: pointer
transition: background $transition-fast
&:hover
opacity: 0.9
Responsive Styling
@use '../base.sass' as *
.container
max-width: $breakpoint-xl
margin: 0 auto
padding: $spacing-md
@media (max-width: $breakpoint-md)
padding: $spacing-sm
.grid
display: grid
grid-template-columns: repeat(3, 1fr)
gap: $spacing-md
@media (max-width: $breakpoint-lg)
grid-template-columns: repeat(2, 1fr)
@media (max-width: $breakpoint-sm)
grid-template-columns: 1fr
Best Practices
Follow these best practices for maintainable styling:
- Define tokens at the top level. Never nest design tokens inside mixins or functions.
- Prefer semantic HTML. Let the structure define the styling instead of adding classes.
- Import base.sass correctly. Always use @use 'relative/path/to/base.sass' as *.
- Keep specificity low. Avoid deeply nested selectors.
- Use variables for consistency. Never hardcode colors, spacing or sizes.