From 8158e5048413a7a17dbf39b505c73049c7ac64ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tr=E1=BA=A7n=20=C4=90=C3=ACnh=20Huy?= <68500260+huytdps13400@users.noreply.github.com> Date: Sun, 4 Oct 2026 10:34:17 +0700 Subject: [PATCH 1/2] feat: support native height media queries --- CONTEXT.md | 1 + .../css-processor/addMetaToStylesTemplate.ts | 7 ++ .../uniwind/src/bundler/css-processor/mq.ts | 94 ++++++++++++++----- .../src/bundler/css-processor/types.ts | 2 + packages/uniwind/src/core/native/store.ts | 8 ++ packages/uniwind/src/core/types.ts | 2 + .../styles-parsing/media-queries.test.ts | 84 +++++++++++++++++ packages/uniwind/tests/test.css | 42 +++++++++ .../references/variants-and-selectors.md | 16 ++++ 9 files changed, 230 insertions(+), 26 deletions(-) diff --git a/CONTEXT.md b/CONTEXT.md index 675cc037..f5984092 100644 --- a/CONTEXT.md +++ b/CONTEXT.md @@ -75,6 +75,7 @@ Native runtime: - Resolved styles subscribe to only dependencies they use, then invalidate cache entries on change. - Runtime dependencies are represented by `StyleDependency`: theme, dimensions, orientation, insets, font scale, RTL, adaptive themes, and variables. - Native style resolution filters rules by screen width, orientation, theme, RTL, active/focus/disabled state, and `data-*` props. +- Native height media queries use separate minimum/maximum height bounds in generated style metadata and subscribe to dimension changes. Height constraints are evaluated against screen height independently of width, including strict comparison boundaries and viewport-relative values. Relative height bounds are emitted as lazy callbacks so resizing does not freeze a viewport-based threshold. Dimension queries support equality, ranges, intervals, and `and` intersections, including platform-qualified conditions. Height minima break ties between rules with equal width minima, preserving existing width-breakpoint precedence. - Native post-processing adapts CSS concepts to RN shapes, including line-height multipliers, shadows, transforms, gradients, visibility, borders, outlines, font variants, and filters. Web runtime: diff --git a/packages/uniwind/src/bundler/css-processor/addMetaToStylesTemplate.ts b/packages/uniwind/src/bundler/css-processor/addMetaToStylesTemplate.ts index d9667122..29cd0980 100644 --- a/packages/uniwind/src/bundler/css-processor/addMetaToStylesTemplate.ts +++ b/packages/uniwind/src/bundler/css-processor/addMetaToStylesTemplate.ts @@ -64,6 +64,8 @@ export const addMetaToStylesTemplate = (Processor: ProcessorBuilder, currentPlat orientation, minWidth, maxWidth, + minHeight, + maxHeight, colorScheme, important: _, importantProperties, @@ -115,6 +117,8 @@ export const addMetaToStylesTemplate = (Processor: ProcessorBuilder, currentPlat if ( Number(minWidth) !== 0 || Number(maxWidth) !== Number.MAX_VALUE + || Number(minHeight) !== 0 + || Number(maxHeight) !== Number.MAX_VALUE || stringifiedEntries.includes('rt.screen') ) { dependencies.push(StyleDependency.Dimensions) @@ -132,6 +136,8 @@ export const addMetaToStylesTemplate = (Processor: ProcessorBuilder, currentPlat entries, minWidth, maxWidth, + minHeight: typeof minHeight === 'number' ? minHeight : `function() { return ${minHeight} }`, + maxHeight: typeof maxHeight === 'number' ? maxHeight : `function() { return ${maxHeight} }`, theme: makeSafeForSerialization(theme), orientation: makeSafeForSerialization(orientation), rtl, @@ -149,6 +155,7 @@ export const addMetaToStylesTemplate = (Processor: ProcessorBuilder, currentPlat dataAttributes, complexity: [ minWidth !== 0, + minHeight !== 0 || maxHeight !== Number.MAX_VALUE, theme !== null, orientation !== null, rtl !== null, diff --git a/packages/uniwind/src/bundler/css-processor/mq.ts b/packages/uniwind/src/bundler/css-processor/mq.ts index 99b0a066..72eb4d5f 100644 --- a/packages/uniwind/src/bundler/css-processor/mq.ts +++ b/packages/uniwind/src/bundler/css-processor/mq.ts @@ -1,6 +1,6 @@ import type { ColorScheme, Orientation } from '@/common/consts' import { Platform } from '@/common/consts' -import type { MediaQuery, QueryFeatureFor_MediaFeatureId } from 'lightningcss' +import type { MediaCondition, MediaQuery, QueryFeatureFor_MediaFeatureId } from 'lightningcss' import type { ProcessorBuilder } from './processor' import type { MediaQueryResolver } from './types' @@ -26,48 +26,84 @@ export class MQ { ].includes(mediaType as Platform) ) { mq.platform = mediaType as Platform - - return - } - - if (condition?.type !== 'feature') { - return - } - - if (condition.value.type === 'range') { - this.processWidthMediaQuery(condition.value, mq) } - if (condition.value.type === 'plain') { - this.processPlainMediaQuery(condition.value, mq) - } + if (condition) this.processCondition(condition, mq) }) return mq } - private processWidthMediaQuery(query: QueryFeatureFor_MediaFeatureId & { type: 'range' }, mq: MediaQueryResolver) { - const { operator, value } = query + private processCondition(condition: MediaCondition, mq: MediaQueryResolver) { + if (condition.type === 'operation' && condition.operator === 'and') { + condition.conditions.forEach(part => this.processCondition(part, mq)) + return + } + + if (condition.type !== 'feature') return + + if (condition.value.type === 'range') { + this.processDimensionMediaQuery(condition.value, mq) + } + + if (condition.value.type === 'plain') { + this.processPlainMediaQuery(condition.value, mq) + } + + if (condition.value.type === 'interval') { + const { name, start, startOperator, end, endOperator } = condition.value + const reverseOperator = { + 'less-than': 'greater-than', + 'less-than-equal': 'greater-than-equal', + 'greater-than': 'less-than', + 'greater-than-equal': 'less-than-equal', + 'equal': 'equal', + } as const + + this.processDimensionMediaQuery({ type: 'range', name, operator: reverseOperator[startOperator], value: start }, mq) + this.processDimensionMediaQuery({ type: 'range', name, operator: endOperator, value: end }, mq) + } + } + + private processDimensionMediaQuery(query: QueryFeatureFor_MediaFeatureId & { type: 'range' }, mq: MediaQueryResolver) { + const { operator, value, name } = query + if (name !== 'width' && name !== 'height') return + + const minKey = name === 'height' ? 'minHeight' : 'minWidth' + const maxKey = name === 'height' ? 'maxHeight' : 'maxWidth' const result = this.Processor.CSS.processValue(value) + const setBound = (key: typeof minKey | typeof maxKey, bound: any, operation: 'min' | 'max') => { + mq[key] = typeof mq[key] === 'number' && typeof bound === 'number' + ? Math[operation](mq[key], bound) + : `Math.${operation}(${mq[key]}, ${bound})` + } - if (operator === 'greater-than-equal') { - mq.minWidth = result + if (operator === 'greater-than-equal' || operator === 'equal') { + setBound(minKey, result, 'max') } if (operator === 'greater-than') { - mq.minWidth = typeof result === 'number' - ? result + EXCLUSIVE_BOUND_EPSILON - : `(${result}) + ${EXCLUSIVE_BOUND_EPSILON}` + setBound( + minKey, + typeof result === 'number' + ? result + EXCLUSIVE_BOUND_EPSILON + : `(${result}) + ${EXCLUSIVE_BOUND_EPSILON}`, + 'max', + ) } - if (operator === 'less-than-equal') { - mq.maxWidth = result + if (operator === 'less-than-equal' || operator === 'equal') { + setBound(maxKey, result, 'min') } if (operator === 'less-than') { - mq.maxWidth = typeof result === 'number' - ? result - EXCLUSIVE_BOUND_EPSILON - : `(${result}) - ${EXCLUSIVE_BOUND_EPSILON}` + setBound( + maxKey, + typeof result === 'number' + ? result - EXCLUSIVE_BOUND_EPSILON + : `(${result}) - ${EXCLUSIVE_BOUND_EPSILON}`, + 'min', + ) } } @@ -75,6 +111,10 @@ export class MQ { const { value, name } = query switch (name) { + case 'width': + case 'height': + this.processDimensionMediaQuery({ type: 'range', name, operator: 'equal', value }, mq) + break case 'orientation': mq.orientation = value.value as Orientation @@ -92,6 +132,8 @@ export class MQ { return { minWidth: 0, maxWidth: Number.MAX_VALUE, + minHeight: 0, + maxHeight: Number.MAX_VALUE, platform: null, rtl: null, important: false, diff --git a/packages/uniwind/src/bundler/css-processor/types.ts b/packages/uniwind/src/bundler/css-processor/types.ts index 204af1f3..a9651417 100644 --- a/packages/uniwind/src/bundler/css-processor/types.ts +++ b/packages/uniwind/src/bundler/css-processor/types.ts @@ -16,6 +16,8 @@ import type { export type MediaQueryResolver = { maxWidth: any minWidth: any + maxHeight: any + minHeight: any platform: Platform | null rtl: boolean | null important: boolean diff --git a/packages/uniwind/src/core/native/store.ts b/packages/uniwind/src/core/native/store.ts index afb5f4a7..0352a17d 100644 --- a/packages/uniwind/src/core/native/store.ts +++ b/packages/uniwind/src/core/native/store.ts @@ -21,6 +21,8 @@ type StylesResult = { const emptyState: StylesResult = { styles: {}, dependencies: [], dependencySum: 0 } +const resolveHeightBound = (bound: Style['minHeight']) => typeof bound === 'function' ? bound() : bound + class UniwindStoreBuilder { runtime = UniwindRuntime vars = {} as Record @@ -148,9 +150,14 @@ class UniwindStoreBuilder { hasDataAttributes = true } + const minHeight = resolveHeightBound(style.minHeight) + const maxHeight = resolveHeightBound(style.maxHeight) + if ( style.minWidth > this.runtime.screen.width || style.maxWidth < this.runtime.screen.width + || minHeight > this.runtime.screen.height + || maxHeight < this.runtime.screen.height || (style.theme !== null && theme !== style.theme) || (style.orientation !== null && this.runtime.orientation !== style.orientation) || (style.rtl !== null && !this.validateDir(style.rtl, uniwindContext)) @@ -167,6 +174,7 @@ class UniwindStoreBuilder { if (previousBest) { const previousWins = previousBest.minWidth > style.minWidth + || (previousBest.minWidth === style.minWidth && resolveHeightBound(previousBest.minHeight) > minHeight) || previousBest.complexity > style.complexity || ( previousBest.complexity === style.complexity diff --git a/packages/uniwind/src/core/types.ts b/packages/uniwind/src/core/types.ts index 54c371df..979175da 100644 --- a/packages/uniwind/src/core/types.ts +++ b/packages/uniwind/src/core/types.ts @@ -10,6 +10,8 @@ export type Style = { entries: Array<[string, Var]> minWidth: number maxWidth: number + minHeight: number | (() => number) + maxHeight: number | (() => number) orientation: Orientation | null theme: ThemeName | null rtl: boolean | null diff --git a/packages/uniwind/tests/native/styles-parsing/media-queries.test.ts b/packages/uniwind/tests/native/styles-parsing/media-queries.test.ts index 05829d24..d8483b92 100644 --- a/packages/uniwind/tests/native/styles-parsing/media-queries.test.ts +++ b/packages/uniwind/tests/native/styles-parsing/media-queries.test.ts @@ -14,6 +14,90 @@ const resolveAtWidth = (className: string, width: number) => { ).styles } +const resolveAtSize = (className: string, width: number, height: number) => { + UniwindStore.runtime.screen = { ...UniwindStore.runtime.screen, width, height } + UniwindListener.notify([StyleDependency.Dimensions]) + + return UniwindStore.getStyles( + className, + {}, + {}, + { scopedTheme: null, rtl: null, variables: null }, + ).styles +} + +describe('height media queries', () => { + const originalScreen = UniwindStore.runtime.screen + + afterEach(() => { + UniwindStore.runtime.screen = originalScreen + UniwindListener.notify([StyleDependency.Dimensions]) + }) + + test.each([ + [699.99, { top: 0, right: 0, bottom: 1, left: 1 }], + [700, { top: 0, right: 1, bottom: 0, left: 1 }], + [700.01, { top: 1, right: 1, bottom: 0, left: 0 }], + ])('resolves height %s independently of width', (height, expected) => { + expect(resolveAtSize('height-query-boundaries', 390, height)).toMatchObject(expected) + expect(resolveAtSize('height-query-boundaries', 1000, height)).toMatchObject(expected) + }) + + test('resolves custom min/max-height variants and invalidates cached styles on resize', () => { + expect(resolveAtSize('tall:top-[1px] compact:right-[1px]', 390, 699)).toEqual({ right: 1 }) + expect(resolveAtSize('tall:top-[1px] compact:right-[1px]', 390, 700)).toEqual({ top: 1, right: 1 }) + expect(resolveAtSize('tall:top-[1px] compact:right-[1px]', 390, 701)).toEqual({ top: 1 }) + expect(resolveAtSize('tall:top-[1px] compact:right-[1px]', 390, 699)).toEqual({ right: 1 }) + }) + + test('resolves viewport-relative height bounds', () => { + expect(resolveAtSize('height-relative-query', 400, 400)).toEqual({}) + expect(resolveAtSize('height-relative-query', 400, 400.01)).toEqual({ top: 1 }) + expect(resolveAtSize('height-relative-query', 401, 400.01)).toEqual({}) + expect(resolveAtSize('height-self-relative', 390, 400)).toEqual({}) + expect(resolveAtSize('height-self-relative', 390, 1000)).toEqual({}) + }) + + test('requires both width and height constraints to match', () => { + expect(resolveAtSize('wide-and-tall', 499, 800)).toEqual({}) + expect(resolveAtSize('wide-and-tall', 600, 699)).toEqual({}) + expect(resolveAtSize('wide-and-tall', 600, 800)).toEqual({ top: 1 }) + }) + + test('prefers the taller minimum-height breakpoint regardless of class order', () => { + expect(resolveAtSize('tall:top-[1px] taller:top-[2px]', 390, 900)).toEqual({ top: 2 }) + expect(resolveAtSize('taller:top-[2px] tall:top-[1px]', 390, 900)).toEqual({ top: 2 }) + }) + + test('keeps max-height variants more specific than unconditional utilities', () => { + expect(resolveAtSize('top-[2px] compact:top-[1px]', 390, 699)).toEqual({ top: 1 }) + expect(resolveAtSize('compact:top-[1px] top-[2px]', 390, 699)).toEqual({ top: 1 }) + expect(resolveAtSize('compact:top-[1px] top-[2px]', 390, 701)).toEqual({ top: 2 }) + }) + + test('supports exact heights and intersects multiple height constraints', () => { + expect(resolveAtSize('exact-height', 390, 699.99)).toEqual({}) + expect(resolveAtSize('exact-height', 390, 700)).toEqual({ top: 1 }) + expect(resolveAtSize('exact-height', 390, 700.01)).toEqual({}) + expect(resolveAtSize('height-intersection', 390, 750)).toEqual({}) + expect(resolveAtSize('height-intersection', 390, 800)).toEqual({ top: 1 }) + }) + + test.each([ + [600, {}], + [600.01, { top: 1 }], + [800, { top: 1 }], + [800.01, {}], + ])('resolves height intervals at %s', (height, expected) => { + expect(resolveAtSize('height-interval', 390, height)).toEqual(expected) + }) + + test('keeps height conditions when stacked with a platform variant', () => { + expect(resolveAtSize('ios:tall:right-[1px] android:tall:left-[1px]', 390, 699)).toEqual({}) + expect(resolveAtSize('ios:tall:right-[1px] android:tall:left-[1px]', 390, 700)).toEqual({ right: 1 }) + }) +}) + describe('media query boundaries', () => { const originalScreen = UniwindStore.runtime.screen diff --git a/packages/uniwind/tests/test.css b/packages/uniwind/tests/test.css index 75059d53..d040daff 100644 --- a/packages/uniwind/tests/test.css +++ b/packages/uniwind/tests/test.css @@ -1,6 +1,48 @@ @import "tailwindcss"; @import "uniwind"; +@custom-variant tall (@media (min-height: 700px)); +@custom-variant taller (@media (min-height: 800px)); +@custom-variant compact (@media (max-height: 700px)); + +.height-query-boundaries { + top: 0; + right: 0; + bottom: 0; + left: 0; +} + +@media (height > 700px) { + .height-query-boundaries { top: 1px; } +} +@media (height >= 700px) { + .height-query-boundaries { right: 1px; } +} +@media (height < 700px) { + .height-query-boundaries { bottom: 1px; } +} +@media (height <= 700px) { + .height-query-boundaries { left: 1px; } +} +@media (height > 100vw) { + .height-relative-query { top: 1px; } +} +@media (height > 100vh) { + .height-self-relative { top: 1px; } +} +@media (min-width: 500px) and (min-height: 700px) { + .wide-and-tall { top: 1px; } +} +@media (height: 700px) { + .exact-height { top: 1px; } +} +@media (min-height: 800px) and (min-height: 700px) { + .height-intersection { top: 1px; } +} +@media (600px < height <= 800px) { + .height-interval { top: 1px; } +} + :root { --foo: 120 50% 50%; --bar: 0.7 0.1 141; diff --git a/skills/uniwind/references/variants-and-selectors.md b/skills/uniwind/references/variants-and-selectors.md index f84e98d0..1517e126 100644 --- a/skills/uniwind/references/variants-and-selectors.md +++ b/skills/uniwind/references/variants-and-selectors.md @@ -151,6 +151,22 @@ Custom breakpoints: Usage: `xs:p-2 tablet:p-4 3xl:p-8` +Height breakpoints use custom variants instead of `--breakpoint-*` (which defines width breakpoints): + +```css +@custom-variant tall (@media (min-height: 700px)); +@custom-variant compact (@media (max-height: 699px)); +``` + +```tsx +Fits taller screens + +``` + +Native height queries respond to dimension changes and can be combined with width or platform variants. +They support `min-height`, `max-height`, exact height, range comparisons and intervals such as +`(600px < height <= 800px)`. Use `and` to require multiple dimension constraints. + **Design mobile-first** — start with base styles (no prefix), enhance with breakpoints: ```tsx From c2b9d031d9c81ce7bddde8e534e0ad1824d9de15 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tr=E1=BA=A7n=20=C4=90=C3=ACnh=20Huy?= <68500260+huytdps13400@users.noreply.github.com> Date: Sun, 4 Oct 2026 22:09:52 +0700 Subject: [PATCH 2/2] fix: preserve height query units and specificity --- CONTEXT.md | 2 +- .../uniwind/src/bundler/css-processor/mq.ts | 6 ++++- packages/uniwind/src/core/native/store.ts | 7 +++++- .../styles-parsing/media-queries.test.ts | 22 +++++++++++++++++++ packages/uniwind/tests/test.css | 7 ++++++ .../references/variants-and-selectors.md | 2 +- 6 files changed, 42 insertions(+), 4 deletions(-) diff --git a/CONTEXT.md b/CONTEXT.md index f5984092..bc3940d9 100644 --- a/CONTEXT.md +++ b/CONTEXT.md @@ -75,7 +75,7 @@ Native runtime: - Resolved styles subscribe to only dependencies they use, then invalidate cache entries on change. - Runtime dependencies are represented by `StyleDependency`: theme, dimensions, orientation, insets, font scale, RTL, adaptive themes, and variables. - Native style resolution filters rules by screen width, orientation, theme, RTL, active/focus/disabled state, and `data-*` props. -- Native height media queries use separate minimum/maximum height bounds in generated style metadata and subscribe to dimension changes. Height constraints are evaluated against screen height independently of width, including strict comparison boundaries and viewport-relative values. Relative height bounds are emitted as lazy callbacks so resizing does not freeze a viewport-based threshold. Dimension queries support equality, ranges, intervals, and `and` intersections, including platform-qualified conditions. Height minima break ties between rules with equal width minima, preserving existing width-breakpoint precedence. +- Native height media queries use separate minimum/maximum height bounds in generated style metadata and subscribe to dimension changes. Height constraints are evaluated against screen height independently of width, including strict comparison boundaries and viewport-relative values. Relative height bounds are emitted as lazy callbacks so resizing does not freeze a viewport-based threshold. Dimension queries support equality, ranges, intervals, and `and` intersections, including platform-qualified conditions. Font-relative media-query units use the configured initial font size, independently of scoped variables or element declarations. Height minima break ties only between equally complex, non-important rules with equal width minima, preserving existing width-breakpoint precedence and more specific theme/state rules. - Native post-processing adapts CSS concepts to RN shapes, including line-height multipliers, shadows, transforms, gradients, visibility, borders, outlines, font variants, and filters. Web runtime: diff --git a/packages/uniwind/src/bundler/css-processor/mq.ts b/packages/uniwind/src/bundler/css-processor/mq.ts index 72eb4d5f..6d2e9eb5 100644 --- a/packages/uniwind/src/bundler/css-processor/mq.ts +++ b/packages/uniwind/src/bundler/css-processor/mq.ts @@ -71,7 +71,11 @@ export class MQ { const minKey = name === 'height' ? 'minHeight' : 'minWidth' const maxKey = name === 'height' ? 'maxHeight' : 'maxWidth' - const result = this.Processor.CSS.processValue(value) + // Media-query em units refer to the initial font size, just like rem, + // rather than an element's (possibly scoped) font-size variable. + const result = value.type === 'length' && value.value.type === 'value' && value.value.value.unit === 'em' + ? this.Processor.Units.processLength({ ...value.value.value, unit: 'rem' }) + : this.Processor.CSS.processValue(value) const setBound = (key: typeof minKey | typeof maxKey, bound: any, operation: 'min' | 'max') => { mq[key] = typeof mq[key] === 'number' && typeof bound === 'number' ? Math[operation](mq[key], bound) diff --git a/packages/uniwind/src/core/native/store.ts b/packages/uniwind/src/core/native/store.ts index 0352a17d..de4216f2 100644 --- a/packages/uniwind/src/core/native/store.ts +++ b/packages/uniwind/src/core/native/store.ts @@ -174,7 +174,12 @@ class UniwindStoreBuilder { if (previousBest) { const previousWins = previousBest.minWidth > style.minWidth - || (previousBest.minWidth === style.minWidth && resolveHeightBound(previousBest.minHeight) > minHeight) + || ( + previousBest.minWidth === style.minWidth + && previousBest.complexity === style.complexity + && !style.importantProperties.includes(property) + && resolveHeightBound(previousBest.minHeight) > minHeight + ) || previousBest.complexity > style.complexity || ( previousBest.complexity === style.complexity diff --git a/packages/uniwind/tests/native/styles-parsing/media-queries.test.ts b/packages/uniwind/tests/native/styles-parsing/media-queries.test.ts index d8483b92..23175118 100644 --- a/packages/uniwind/tests/native/styles-parsing/media-queries.test.ts +++ b/packages/uniwind/tests/native/styles-parsing/media-queries.test.ts @@ -58,6 +58,28 @@ describe('height media queries', () => { expect(resolveAtSize('height-self-relative', 390, 1000)).toEqual({}) }) + test('resolves em height bounds against the initial font size', () => { + expect(resolveAtSize('height-em-query height-em-max-query', 390, 639)).toEqual({ right: 1 }) + expect(resolveAtSize('height-em-query height-em-max-query', 390, 640)).toEqual({ top: 1, right: 1 }) + expect(resolveAtSize('height-em-query height-em-max-query', 390, 641)).toEqual({ top: 1 }) + }) + + test.each([ + 'taller:top-[2px] dark:tall:top-[1px]', + 'dark:tall:top-[1px] taller:top-[2px]', + ])('keeps theme specificity ahead of height in %s', className => { + resolveAtSize('', 390, 900) + expect(UniwindStore.getStyles(className, {}, {}, { scopedTheme: 'dark', rtl: null, variables: null }).styles) + .toEqual({ top: 1 }) + }) + + test.each([ + 'taller:top-[2px] tall:top-[1px]!', + 'tall:top-[1px]! taller:top-[2px]', + ])('keeps important declarations ahead of height in %s', className => { + expect(resolveAtSize(className, 390, 900)).toEqual({ top: 1 }) + }) + test('requires both width and height constraints to match', () => { expect(resolveAtSize('wide-and-tall', 499, 800)).toEqual({}) expect(resolveAtSize('wide-and-tall', 600, 699)).toEqual({}) diff --git a/packages/uniwind/tests/test.css b/packages/uniwind/tests/test.css index d040daff..d750723e 100644 --- a/packages/uniwind/tests/test.css +++ b/packages/uniwind/tests/test.css @@ -43,6 +43,13 @@ .height-interval { top: 1px; } } +@media (min-height: 40em) { + .height-em-query { top: 1px; } +} +@media (max-height: 40em) { + .height-em-max-query { right: 1px; } +} + :root { --foo: 120 50% 50%; --bar: 0.7 0.1 141; diff --git a/skills/uniwind/references/variants-and-selectors.md b/skills/uniwind/references/variants-and-selectors.md index 1517e126..98b33840 100644 --- a/skills/uniwind/references/variants-and-selectors.md +++ b/skills/uniwind/references/variants-and-selectors.md @@ -155,7 +155,7 @@ Height breakpoints use custom variants instead of `--breakpoint-*` (which define ```css @custom-variant tall (@media (min-height: 700px)); -@custom-variant compact (@media (max-height: 699px)); +@custom-variant compact (@media (max-height: 700px)); ``` ```tsx