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 tocreateCss()
. This lets you tailor the responsive design system to your app’s exact needs, while still retaining core features likedark
,light
,hover
,reducedMotion
. When you define custom breakpoints, thecss.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 (likenthChild
,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.