Repository navigation
v3.11.0
Minor Changes
-
#400
0e98c14Thanks @tenphi! - Tasty's default@propertyset is now the only source of the defaults style handlers read. The handlers no longer repeat them as inlinevar()fallbacks:transitionemitsvar(--name-transition, var(--transition))instead ofvar(--name-transition, var(--transition, 80ms)).preset: 'name / strong'emitsfont-weight: var(--bold-font-weight)instead ofvar(--bold-font-weight, 700).fontemitsvar(--font-sans, var(--font-sans-fallback))andvar(--font-mono, var(--font-mono-fallback))without the short inner font stacks.borderand the logical border styles emitvar(--border-color)instead ofvar(--border-color, currentColor)for a border with no color, asoutlinealready does forvar(--outline-color).
Every rendering path emits the default set: the runtime injector, the SSR collector, and the Babel plugin.
extractConfigProperties()is now exported from@tenphi/tasty/zero, so CSS built with the programmatic API can include it too. It returns Tasty's default@propertyrules overlaid with thepropertiesset throughconfigure(), or with the ones you pass.If you write CSS with
extractStylesWithChunks()orextractStylesForSelector(), emitextractConfigProperties()once per stylesheet. Otherwise a border with no color,transition, andpresetwithstrongresolve to nothing. In engines without@propertysupport (Firefox before 128, Safari before 16.4), these defaults no longer apply unless the app defines the tokens itself. -
#396
1a6711dThanks @tenphi! - Setvariantintasty({ variants, variant: 'primary' })to choose the variant applied when thevariantprop is omitted or names no variant. Before, that option was passed to the DOM as avariantattribute, and without adefaultkey an omittedvariantapplied only the base styles. -
#396
1a6711dThanks @tenphi! - The extension form,tasty(Base, options), now does what its options type-check for, instead of silently ignoring them:stylePropsturns the listed props into styles,modPropsinto mods andtokenPropsinto tokens. Before, they reachedBaseas unknown props, andmodPropsended up as DOM attributes.variantsapply on top of the extension'sstyles(thevariantit forwards still selects the base's own variants), and thevariantoption sets the default.elementsadds sub-elements, and the base's sub-elements are kept:tasty(Card, …).TitleisCard.Title.
The returned component is typed with the props these options add, its own sub-elements, and the base's.
getDisplayName()now looks throughforwardRef()andmemo()to the component they wrap, and names a tag after itself, so an extension of aforwardRefcomponent is no longer namedTastyWrappedComponent(Anonymous). -
#396
1a6711dThanks @tenphi! - Add@tenphi/tasty/ssr/client, a framework-neutral, browser-safe entry that exportshydrateTastyClasses()with no import side effects. Call it beforehydrateRoot().@tenphi/tasty/ssris also safe to import in a browser bundle now. It used to break rendering with "AsyncLocalStorage is not a constructor" once the bundler stubbed outnode:async_hooks; the collector lookup now treats a stub as "no server collector", andrunWithCollector()throws a clear error there instead.
Patch Changes
-
#394
f898b31Thanks @tenphi! - Container queries over size features other than width/height/inline-size/block-size now work:@(aspect-ratio > 1)and@(orientation: portrait)compile to@container (aspect-ratio > 1)and@container (orientation: portrait). Before, they became an invalid selector and the style never applied. -
#396
1a6711dThanks @tenphi! -tasty()components no longer render unstyled aftergc(),cleanup()ordestroy()deleted their rules: the class name cached for the component's styles is dropped with them, and the next render injects the CSS again.Re-rendering also does less work. Instance
stylesthat stay the same object between renders — an extension's defaults, a module-level constant, a memoized object — are merged and computed once, where they used to go throughmergeStyles()andcomputeStyles()on every render. Everytasty(Base, { styles })extension benefits. -
#400
0e98c14Thanks @tenphi! - Fix style handlers that emitted CSS the browser dropped or resolved inconsistently:transition: 'zIndex'compiled tozindex, which names no property. It now transitionsz-index, with the$z-index-transitiontiming token, exactly astransition: 'z-index'does.- A group that names no value (
padding: 'top',margin: 'top',radius: 'top-right',border: 'dotted left',outline: 'dotted',fade: 'top',width: 'min', and the logicalblockPadding/inlineMarginforms) read token variables (var(--gap),var(--radius),var(--border-width),var(--outline-width)) instead of the units. It now uses the same unit as the boolean form (1x,1r,1bw,1ow;2xforfade), soconfigure({ units })applies to both, andpadding: 'top'is no longer half ofpadding: truewhen no$gaptoken is set. - Units in
@mediaand@containerconditions.@media(w < 40x)emittedcalc(var(--gap) * 40), which no browser evaluates, so neither branch applied, and units other thanxpassed through raw. Every unit, built-in or configured, now resolves to its literal value (320px). A unit configured as a custom property still cannot work in a query condition and is reported in development. stretchonwidth,height,blockSizeandinlineSize.width: 'stretch'emitted one comma-joined value that browsers drop, and the other sizes becameauto. Each now emits-webkit-fill-available,-moz-availableandstretchas separate declarations, so every engine keeps the last one it supports. More generally, a style handler that returns an array for a property now gets one declaration per entry, as theCSSMaptype already allowed.fillwithimage.image,backgroundImage,backgroundColor,backgroundand the other background longhands rendered in a different chunk fromfill, so a two-colorfilland animageeach wrotebackground-imagefrom their own class and rule order picked one. They now render together, and the image is layered over the second fill color as intended.fill: trueemittedbackground-color: true. There is no default fill color, so it is now ignored with a development warning.@property --current-colorwas registered twice in SSR output, once with its defaultinitial-value: currentcolorand again, auto-inferred, astransparent. SSR and RSC auto-inference now skips properties Tasty already declares (its defaults andconfigure({ properties })), as zero-runtime extraction already does.$gapnow starts at8px, matching thexunit like$radius,$border-widthand$outline-widthalready match theirs. Pointing the unit at the token (units: { x: 'var(--gap)' }) no longer halves everyxvalue until$gapis defined.- Hex literals.
color: '#ddd'stayed a bare literal while every other color style emittedvar(--ddd-color, #ddd), and an opacity suffix (#ddd.5) dropped the literal fallback entirely, so it resolved to nothing without a#dddtoken.colornow reads hex the same way as the other styles, and the opacity form keeps its fallback.
-
#396
1a6711dThanks @tenphi! -inject()without acacheKeyreuses the class when the same rules are injected again, as it does for a repeatedcacheKey. It used to write every rule a second time and reset the class's pin count to 1, so disposing one caller's handle could letgc()delete rules another caller still held. -
#396
1a6711dThanks @tenphi! - Correct stale JSDoc in the published type declarations: units are literal (2x→16px,2r→12px) rather thancalc(… var(--gap)), theunitsexample no longer redefines nativeem/vw,tastyDebug.help()shows a hash-based class name, andgetCSSText()is documented as needing a DOM rather than being "for SSR". -
#393
62579c7Thanks @tenphi! - Resolution queries now keep CSS's ownxunit:@media(min-resolution: 2x)and@media(resolution >= 2x)reach the browser as written instead of having2xrewritten as a spacing length, which stopped them from ever matching. Tasty units are still converted inwidth,height,inline-sizeandblock-sizeconditions, includingmin-width: 40x. Range syntax over other features (@media(aspect-ratio > 16/9)) now passes through as a media query instead of becoming an invalid selector. -
#390
a177eafThanks @tenphi! - Make#cleara default replace token fortransparent. Numeric and custom-property opacity suffixes on transparent aliases are ignored, so#clear.5stays transparent. Remove the predefined@property --clear-color; CSS and local token overrides of--clear-colorno longer affect#clear. Use another color token or explicitly overridereplaceTokens['#clear']to customize it. -
#395
384af41Thanks @tenphi! -outlinewith no color is now drawn in the text color until your tokens define#outline. Tasty registers#outlineby default (initial-value: currentcolor), as it does#border; before,outline: trueoroutline: '2px dashed'drew nothing unless the app defined the token. A style that sets#outlinelocally also no longer turns every other outline transparent in runtime or zero-runtime CSS. -
#396
1a6711dThanks @tenphi! - React Server Components fixes:- The main entry loads under the
react-servercondition. React's server build has nocreateContext,useContextoruseInsertionEffect, and named imports of them stopped@tenphi/tastyfrom loading in a plain Node process run with--conditions react-server. useStyles()works in Server Components. It skips theTastyRegistrycontext lookup there, and queues its CSS for the nexttasty()element to emit, as the other style functions do.- Rendering on the server without a request scope now warns in development. In React's standard build — Astro without
tastyIntegration(), or a client component server-rendered withoutTastyRegistry—React.cachedoes not cache, so eachtasty()element inlines all of its CSS, and configured globals (tokens,@propertydefaults,globalStyles,@font-face) and the CSS of the standalone style functions are not emitted. Tasty now says so once, and points totastyIntegration(),TastyRegistryorrunWithCollector().
- The main entry loads under the
-
#397
89ee2acThanks @tenphi! - Media and container query bounds in different units are no longer compared as plain numbers. Before,600pxand40emwere ordered as 600 vs 40, so@media(w >= 600px) & @media(w < 40em)was dropped as an empty range.@media(w > 600px) & @media(w > 40em)lost itsembound, and aw < 40embranch followed byw < 600pxnever applied. Bounds are now compared, merged and pruned only within one unit, andcalc()bounds are kept instead of being dropped next to a numeric one. -
#398
fdf1401Thanks @tenphi! - Zero-runtime CSS now keeps the defaults that style handlers read from Tasty's default tokens. That CSS ships without the default@propertyset, so several handlervar()s had no value there.transitionwith no duration now falls back to80ms(var(--transition, 80ms)) instead of producing an invalidtransitionthat disabled the animation.preset: '… / strong'now bolds to700instead of inheriting the weight.font: true,font: 'monospace', a customfontand preset font families now fall back tosystem-ui, sans-seriforui-monospace, monospaceinstead of the inherited font (a customfontwas dropped entirely). Define$font-sans/$font-mono(or the-fallbacktokens) for the full stacks runtime uses. Runtime and SSR output renders the same as before for all three.Visible change: a
borderwith no color is now drawn in the text color (currentColor) in every mode until your tokens define#border. Tasty's default#borderproperty now starts atcurrentcolorinstead ofrgb(0 0 0). Before, those borders were black at runtime and in SSR but used the text color in zero-runtime CSS. Apps that define#border, as most design systems do, see no change. To keep black borders, define#borderin your tokens. -
#396
1a6711dThanks @tenphi! - The Babel plugin (@tenphi/tasty/babel-plugin) and the Next.js wrappers (@tenphi/tasty/zero/next,@tenphi/tasty/ssr/next-config) no longer import the optionaljitipeer up front. It is loaded only whenconfigFileis used, so a project that passesconfigdirectly no longer needsjitiinstalled. Without it,configFilefails with an error that says to install it. -
#396
1a6711dThanks @tenphi! - The Babel plugin now emits the@propertyrules the runtime registers: Tasty's defaults (#white,#black,#clear,#border,$gap,$radius,$border-width,$outline-width, …) andconfig.properties, which previously type-checked but was ignored. Static CSS that readsvar(--white-color)orvar(--radius)no longer depends on the token being configured. In file mode the rules are written once at the top of the output; in inject mode they are injected once per file that callstastyStatic(). Automatic@propertyinference leaves these properties alone, so the declared definition is the one that applies.