Styling

Spark provides a typed CSS-in-Dart solution for styling your pages and components. You can use the string-based Style constructor for quick styling, or the type-safe Style.typed constructor with full IDE autocomplete and compile-time validation.

Page Styles

Override the inlineStyles getter in your SparkPage to inject CSS into the <head>.

@override
Stylesheet? get inlineStyles => css({
  'body': Style(
    backgroundColor: 'white',
    color: '#333',
    fontFamily: 'sans-serif',
  ),
  '.container': Style(
    maxWidth: '800px',
    margin: '0 auto',
  ),
});

Type-Safe Styling with Style.typed

The Style.typed constructor provides compile-time type safety and IDE autocomplete for CSS properties. Instead of passing strings, you use typed CSS value classes.

@override
Stylesheet? get inlineStyles => css({
  'body': .typed(
    backgroundColor: .white,
    color: .hex('333'),
    fontFamily: .sansSerif,
  ),
  '.container': .typed(
    maxWidth: .px(800),
    margin: .symmetric(.zero, .auto),
  ),
});

Benefits of Style.typed:

  • Compile-time validation of CSS values
  • IDE autocomplete for all CSS properties and values
  • No typos in property names or values
  • Self-documenting code

CSS Colors

The CssColor class supports all CSS color formats:

// Named colors
CssColor.black
CssColor.white
CssColor.transparent
CssColor.currentColor

// Hex colors
CssColor.hex('ff0000')      // #ff0000
CssColor.hex('#00ff00')     // #00ff00

// RGB / RGBA
CssColor.rgb(255, 0, 0)           // rgb(255, 0, 0)
CssColor.rgba(255, 0, 0, 0.5)     // rgba(255, 0, 0, 0.5)

// HSL / HSLA
CssColor.hsl(0, 100, 50)          // hsl(0, 100%, 50%)
CssColor.hsla(0, 100, 50, 0.5)    // hsla(0, 100%, 50%, 0.5)

// CSS variables
CssColor.variable('primary')     // var(--primary)

// Raw escape hatch
CssColor.raw('color-mix(in srgb, red 50%, blue)')

CSS Lengths

The CssLength class supports all CSS length units:

// Absolute units
CssLength.px(16)              // 16px
CssLength.zero                // 0

// Relative units
CssLength.em(1.5)             // 1.5em
CssLength.rem(1)              // 1rem
CssLength.percent(100)        // 100%

// Viewport units
CssLength.vw(50)              // 50vw
CssLength.vh(100)             // 100vh
CssLength.dvh(100)            // 100dvh (dynamic)

// Keywords
CssLength.auto                // auto
CssLength.maxContent          // max-content
CssLength.minContent          // min-content
CssLength.fitContent          // fit-content

// CSS functions
CssLength.calc('100% - 60px')                    // calc(100% - 60px)
CssLength.min([CssLength.px(100), CssLength.percent(50)])  // min(100px, 50%)
CssLength.max([CssLength.px(100), CssLength.percent(50)])  // max(100px, 50%)
CssLength.clamp(CssLength.px(10), CssLength.percent(50), CssLength.px(100))

// CSS variables
CssLength.variable('spacing')  // var(--spacing)

CSS Spacing

The CssSpacing class handles margin and padding with support for CSS shorthand syntax:

// Single value (all sides)
CssSpacing.all(CssLength.px(16))              // 16px
CssSpacing.zero                               // 0

// Two values (vertical | horizontal)
CssSpacing.symmetric(
  CssLength.px(10),   // vertical
  CssLength.px(20),   // horizontal
)                                             // 10px 20px

// Three values (top | horizontal | bottom)
CssSpacing.only(
  top: CssLength.px(10),
  horizontal: CssLength.px(20),
  bottom: CssLength.px(30),
)                                             // 10px 20px 30px

// Four values (top | right | bottom | left)
CssSpacing.trbl(
  CssLength.px(10),   // top
  CssLength.px(20),   // right
  CssLength.px(30),   // bottom
  CssLength.px(40),   // left
)                                             // 10px 20px 30px 40px

Layout Properties

Type-safe values for display, position, and flexbox:

// Display
CssDisplay.flex
CssDisplay.grid
CssDisplay.block
CssDisplay.inline
CssDisplay.inlineBlock
CssDisplay.inlineFlex
CssDisplay.none

// Position
CssPosition.static_
CssPosition.relative
CssPosition.absolute
CssPosition.fixed
CssPosition.sticky

// Flexbox
CssFlexDirection.row
CssFlexDirection.column
CssFlexDirection.rowReverse
CssFlexDirection.columnReverse

CssJustifyContent.flexStart
CssJustifyContent.flexEnd
CssJustifyContent.center
CssJustifyContent.spaceBetween
CssJustifyContent.spaceAround
CssJustifyContent.spaceEvenly

CssAlignItems.flexStart
CssAlignItems.flexEnd
CssAlignItems.center
CssAlignItems.stretch
CssAlignItems.baseline

Typography

Type-safe values for font and text properties:

// Font weight
CssFontWeight.normal
CssFontWeight.bold
CssFontWeight.w100  // through w900
CssFontWeight.w400
CssFontWeight.w700

// Font family
CssFontFamily.sansSerif
CssFontFamily.serif
CssFontFamily.monospace
CssFontFamily.systemUi
CssFontFamily.named('Helvetica Neue')  // "Helvetica Neue"
CssFontFamily.stack([                   // font stack with fallbacks
  CssFontFamily.named('Inter'),
  CssFontFamily.sansSerif,
])

// Text align
CssTextAlign.left
CssTextAlign.center
CssTextAlign.right
CssTextAlign.justify

// Text decoration
CssTextDecoration.none
CssTextDecoration.underline
CssTextDecoration.lineThrough

// Text transform
CssTextTransform.uppercase
CssTextTransform.lowercase
CssTextTransform.capitalize
CssTextTransform.none

Visual Properties

Type-safe values for visual styling:

// Overflow
CssOverflow.visible
CssOverflow.hidden
CssOverflow.scroll
CssOverflow.auto

// Cursor
CssCursor.pointer
CssCursor.default_
CssCursor.text
CssCursor.move
CssCursor.notAllowed
CssCursor.grab
CssCursor.grabbing

// Numbers (opacity, line-height, flex-grow, etc.)
CssNumber(0.5)                // 0.5
CssNumber(1.6)                // 1.6

// Z-index
CssZIndex(100)                // 100
CssZIndex.auto                // auto

Borders and Transitions

// Border shorthand
CssBorder(
  width: CssLength.px(1),
  style: CssBorderStyle.solid,
  color: CssColor.variable('border-color'),
)                                         // 1px solid var(--border-color)

CssBorder.none                            // none

// Border style
CssBorderStyle.solid
CssBorderStyle.dashed
CssBorderStyle.dotted
CssBorderStyle.none

// Transition
CssTransition.simple('color', '0.2s')                    // color 0.2s
CssTransition.simple('all', '0.3s', CssTimingFunction.ease)  // all 0.3s ease

// Multiple transitions
CssTransition.multiple([
  CssTransition.simple('opacity', '200ms'),
  CssTransition.simple('transform', '300ms'),
])                                        // opacity 200ms, transform 300ms

// Timing functions
CssTimingFunction.ease
CssTimingFunction.easeIn
CssTimingFunction.easeOut
CssTimingFunction.easeInOut
CssTimingFunction.linear
CssTimingFunction.cubicBezier(0.4, 0, 0.2, 1)

CSS Variables

Define CSS custom properties using the add() method and reference them with .variable():

// Define CSS variables in :root
':root': Style()
  ..add('--primary', '#007bff')
  ..add('--spacing', '16px')
  ..add('--border-color', '#eaeaea'),

// Use variables with .typed
'.button': .typed(
  backgroundColor: .variable('primary'),
  padding: .variable('spacing'),
  borderColor: .variable('border-color'),
)

Global Keywords

CSS global keywords are available on all type classes via .global():

// Inherit from parent
CssColor.global(CssGlobal.inherit)

// Reset to initial value
CssLength.global(CssGlobal.initial)

// Unset (inherit if inheritable, else initial)
CssDisplay.global(CssGlobal.unset)

// Revert to user-agent stylesheet
CssPosition.global(CssGlobal.revert)

Raw Escape Hatch

For complex or unsupported CSS values, use the .raw() factory:

// Complex color functions
CssColor.raw('color-mix(in srgb, red 50%, blue)')

// Complex borders with variables
CssBorder.raw('1px solid var(--border-color)')

// Vendor prefixes or experimental features
.typed(
  backdropFilter: 'blur(10px)',  // String for complex values
  transform: 'translateY(-2px) scale(1.05)',
  boxShadow: '0 4px 6px rgba(0, 0, 0, 0.1)',
)

Complete Example

Here's a complete example using Style.typed:

Stylesheet? get inlineStyles => css({
  '.card': .typed(
    display: .flex,
    flexDirection: .column,
    padding: .all(.px(24)),
    margin: .symmetric(
      .px(16),
      .zero,
    ),
    backgroundColor: .white,
    borderRadius: .px(8),
    border: CssBorder(
      width: .px(1),
      style: .solid,
      color: .variable('border-color'),
    ),
    boxShadow: '0 2px 4px rgba(0, 0, 0, 0.1)',
    transition: .simple(
      'box-shadow',
      '0.2s',
      .ease,
    ),
  ),
  '.card:hover': .typed(
    boxShadow: '0 4px 12px rgba(0, 0, 0, 0.15)',
  ),
  '.card-title': .typed(
    fontSize: .rem(1.5),
    fontWeight: .w600,
    color: .variable('text-main'),
    marginBottom: .px(8),
  ),
  '.card-content': .typed(
    fontSize: .rem(1),
    lineHeight: CssNumber(1.6),
    color: .variable('text-muted'),
  ),
});

Component Styles

For Web Components, include styles inside the shadow root using a <style> tag with the typed CSS syntax:

class MyButton extends WebComponent {
  static const tag = 'my-button';

  @override
  String get tagName => tag;

  static dynamic render({required String label}) {
    return element(
      tag,
      [
        template(shadowrootmode: 'open', [
          style([
            css({
              ':host': .typed(display: .inlineBlock),
              'button': .typed(
                padding: .symmetric(.px(8), .px(16)),
                background: 'var(--primary)',
                color: .white,
                border: .none,
                borderRadius: .px(4),
                cursor: .pointer,
              ),
              'button:hover': .typed(opacity: CssNumber(0.9)),
            }).toCss(),
          ]),
          button([label]),
        ]),
      ],
    );
  }
}