TriFrost

TriFrost 0.19.0 - Velvet Edge

|peterver

News

This release further sharpens TriFrost’s styling system with custom breakpoints, ergonomic attribute helpers, and expanded selector coverage — giving you more precision and control without added complexity. Most of these improvements/additions came directly from working on the TriFrost website and as such solve some real-world ergonomic issues.

Added

  • feat: The CSS system now supports custom breakpoints — you can fully replace the default media query set (mobile, tablet, tabletOnly, desktop) by passing your own named breakpoints to createCss(). This lets you tailor the responsive design system to your app’s exact needs, while still retaining core features like dark, light, hover, reducedMotion. When you define custom breakpoints, the css.media object will have them fully typed and ready for usage.
const css = createCss({
  ...,
  breakpoints: {
    sm: '@media (max-width: 640px)',
    md: '@media (max-width: 768px)',
    lg: '@media (max-width: 1024px)',
    xl: '@media (max-width: 1280px)',
  },
});

...

const cls = css({
  fontSize: '1rem',
  [css.media.sm]: {fontSize: '0.875rem'},
  [css.media.md]: {fontSize: '1rem'},
});
  • feat: New attribute selector helpers css.attr, css.attrStartsWith, css.attrEndsWith, css.attrContains. These are there to improve readability and avoid manually writing raw string selectors (and potentially forgetting that closing ], I know I have).
const cls = css({
  [css.attr('data-enabled')]: {color: 'blue'}, /* [data-enabled] */
  [css.attr('data-active', true)]: {color: 'green'}, /* [data-active="true"] */
  [css.attrStartsWith('data-role', 'adm')]: {fontWeight: 'bold'}, /* [data-role^="adm"] */
  [css.attrEndsWith('data-id', '42')]: {opacity: 0.5}, /* [data-id$="42"] */
  [css.attrContains('data-label', 'part')]: {textDecoration: 'underline'}, /* [data-label*="part"] */
});
  • feat: New ergonomic selectors css.firstOfType, css.lastOfType, css.empty. These join the already rich selector toolkit (like nthChild, nthOfType, not, is, etc.) to make even advanced CSS states easy to target.
const cls = css({
  [css.firstOfType]: {marginTop: 0}, /* :first-of-type */
  [css.lastOfType]: {marginBottom: 0},
});

Improved

  • feat: .is() selector can now be passed a set of tags like: css.is('h1', 'h2', 'h3')
const cls = css({
  [`> ${css.is('h1', 'h2', 'h3')}`]: {
    fontSize: '2rem',
    marginBottom: '1rem',
  },
});
/* Generates: .<class> > :is(h1, h2, h3) { ... } */
  • feat: Known HTML tags as well as combinators (>, +, ~) will now be auto-spaced so nested selectors and combinators work cleanly. Previously you'd have to manually space prefix them {[' section']: {[' p']: ...}}, but now:
/* Nested tag selectors */
const cls1 = css({
  section: {
    h2: { fontWeight: 'bold' },
    p: { lineHeight: 1.4 },
  },
});
/**
 * Generates:
 * .<class> section h2 { font-weight: bold }
 * .<class> section p { line-height: 1.4 }
 */

/* Combining known tags with pseudo classes */
const cls2 = css({
  a: {
    textDecoration: 'none',
    [css.hover]: {
      textDecoration: 'underline',
    },
  },
});
/**
 * Generates:
 * .<class> a:hover { text-decoration: underline }
 * .<class> a { text-decoration: none }
 */

/* Handling combinators with nested tag selectors */
const cls3 = css({
  '>': {
    section: {
      h2: { fontWeight: 'bold' },
      p: { lineHeight: 1.4 },
    },
  },
});
/**
 *  Generates:
 * .<class> > section h2 { font-weight: bold }
 * .<class> > section p { line-height: 1.4 }
 */
  • feat: The style engine will no longer automatically prepend styles if no Style marker is found, this prevents issues where html responses purely containing a component render get the entire root style block injected into them. Previously the only way to prevent style injection would have been to pass {inject: false} to each css call — but with multi-layer components, this was a DX blocker.

Loved the read? Share it with others