[READ-ONLY] Mirror of https://github.com/lukebennett88/luke-ui. luke-ui.netlify.app/
0

Configure Feed

Select the types of activity you want to include in your feed.

Add breakpoints documentation (#189)

* Add a Breakpoints overview page with a simulated viewport preview

* Distill breakpoint documentation

* Rename the base breakpoint to initial

* Delete changeset

authored by

Luke Bennett and committed by
GitHub
(Aug 2, 2026, 3:13 PM +1000) 1b3cca67 6418f322

+128 -75
+3 -3
docs/STYLING.md
··· 309 309 ```tsx 310 310 const responsive = createSprinkles({ 311 311 display: 'flex', 312 - flexDirection: { xsmall: 'column', medium: 'row' }, 313 - gap: { xsmall: '300', medium: '600' }, 312 + flexDirection: { initial: 'column', medium: 'row' }, 313 + gap: { initial: '300', medium: '600' }, 314 314 }); 315 315 ``` 316 316 317 - The retained breakpoints are `xsmall` (base), `small` (640px), `medium` (768px), `large` (1024px), 317 + The retained breakpoints are `initial` (base), `small` (640px), `medium` (768px), `large` (1024px), 318 318 `xlarge` (1280px), and `xxlarge` (1536px). 319 319 320 320 ## React Aria `render` prop
+33 -22
packages/@luke-ui/rainbow-sprinkles/src/types.ts
··· 153 153 }; 154 154 155 155 // Mapped config types produced by defineProperties, preserving literal scale key types. 156 + type ConfigConditionalPropertyValue<ConditionName extends string> = { 157 + default: string; 158 + conditions: Record<ConditionName, string>; 159 + }; 160 + 156 161 type DynamicConfigEntry< 157 162 K extends string, 158 163 Scale extends Record<string, unknown> | true, 159 - HasCond extends boolean, 164 + ConditionName extends string, 160 165 > = Scale extends true 161 - ? HasCond extends true 166 + ? [ConditionName] extends [never] 162 167 ? { 163 - dynamic: ConditionalPropertyValue; 164 - vars: ConditionalPropertyValue; 165 - dynamicScale: true; 166 - name: K; 167 - } 168 - : { 169 168 dynamic: NonConditionalPropertyValue; 170 169 vars: NonConditionalPropertyValue; 171 170 dynamicScale: true; 172 171 name: K; 173 172 } 173 + : { 174 + dynamic: ConfigConditionalPropertyValue<ConditionName>; 175 + vars: ConfigConditionalPropertyValue<ConditionName>; 176 + dynamicScale: true; 177 + name: K; 178 + } 174 179 : Scale extends Record<string, unknown> 175 - ? HasCond extends true 180 + ? [ConditionName] extends [never] 176 181 ? { 177 - dynamic: ConditionalPropertyValue; 178 - vars: ConditionalPropertyValue; 182 + dynamic: NonConditionalPropertyValue; 183 + vars: NonConditionalPropertyValue; 179 184 dynamicScale: { [T in keyof Scale & string]: string }; 180 185 name: K; 181 186 } 182 187 : { 183 - dynamic: NonConditionalPropertyValue; 184 - vars: NonConditionalPropertyValue; 188 + dynamic: ConfigConditionalPropertyValue<ConditionName>; 189 + vars: ConfigConditionalPropertyValue<ConditionName>; 185 190 dynamicScale: { [T in keyof Scale & string]: string }; 186 191 name: K; 187 192 } ··· 190 195 type StaticConfigEntry< 191 196 K extends string, 192 197 Scale extends ReadonlyArray<string> | Record<string, unknown>, 193 - HasCond extends boolean, 198 + ConditionName extends string, 194 199 > = 195 200 Scale extends ReadonlyArray<infer V extends string> 196 - ? HasCond extends true 197 - ? { values: { [X in V]: ConditionalPropertyValue }; staticScale: Scale; name: K } 198 - : { values: { [X in V]: NonConditionalPropertyValue }; staticScale: Scale; name: K } 201 + ? [ConditionName] extends [never] 202 + ? { values: { [X in V]: NonConditionalPropertyValue }; staticScale: Scale; name: K } 203 + : { 204 + values: { [X in V]: ConfigConditionalPropertyValue<ConditionName> }; 205 + staticScale: Scale; 206 + name: K; 207 + } 199 208 : Scale extends Record<string, unknown> 200 - ? HasCond extends true 209 + ? [ConditionName] extends [never] 201 210 ? { 202 - values: { [T in keyof Scale & string]: ConditionalPropertyValue }; 211 + values: { [T in keyof Scale & string]: NonConditionalPropertyValue }; 203 212 staticScale: { [T in keyof Scale & string]: string }; 204 213 name: K; 205 214 } 206 215 : { 207 - values: { [T in keyof Scale & string]: NonConditionalPropertyValue }; 216 + values: { 217 + [T in keyof Scale & string]: ConfigConditionalPropertyValue<ConditionName>; 218 + }; 208 219 staticScale: { [T in keyof Scale & string]: string }; 209 220 name: K; 210 221 } ··· 219 230 [K in keyof Dyn & string]: DynamicConfigEntry< 220 231 K, 221 232 NonNullable<Dyn[K]>, 222 - Cond extends undefined ? false : true 233 + Cond extends ConfigConditions ? keyof Cond & string : never 223 234 >; 224 235 } 225 236 : Record<never, never>) & ··· 228 239 [K in keyof Stat & string]: StaticConfigEntry< 229 240 K, 230 241 NonNullable<Stat[K]>, 231 - Cond extends undefined ? false : true 242 + Cond extends ConfigConditions ? keyof Cond & string : never 232 243 >; 233 244 } 234 245 : Record<never, never>);
+45
apps/docs/content/docs/overview/breakpoints.mdx
··· 1 + --- 2 + title: Breakpoints 3 + description: Change layout at named viewport widths with responsive layout properties. 4 + --- 5 + 6 + Luke UI defines six breakpoint keys for responsive layout. `Box` layout properties accept a value at 7 + each key. Choose a breakpoint when content needs a deliberate layout change. 8 + 9 + Read [Layout](/overview/layout) for the full layout API. 10 + 11 + ## Breakpoints 12 + 13 + | Breakpoint | Minimum viewport width | 14 + | ---------- | ---------------------- | 15 + | `initial` | 0px (base) | 16 + | `small` | 640px | 17 + | `medium` | 768px | 18 + | `large` | 1024px | 19 + | `xlarge` | 1280px | 20 + | `xxlarge` | 1536px | 21 + 22 + Each breakpoint is a fixed constant, not a theme token. A custom theme cannot change these widths. 23 + 24 + Every breakpoint is a minimum width. There is no maximum-width or range condition. A value set at 25 + `medium` also applies at `large`, `xlarge`, and `xxlarge`, unless a later breakpoint overrides it. 26 + 27 + ## Responsive values 28 + 29 + Pass a direct value when a layout property stays constant. Pass an object keyed by breakpoint when 30 + it changes. Values cascade up from `initial`, so specify only the changes. 31 + 32 + ```tsx 33 + <Box display="flex" flexDirection={{ initial: 'column', medium: 'row' }}> 34 + {children} 35 + </Box> 36 + ``` 37 + 38 + This example stacks the children by default. It places them in a row from `medium`. 39 + 40 + <ExampleBlock src="box/responsive-layout" title="Breakpoints — A row from medium" /> 41 + 42 + ## When to use a responsive value 43 + 44 + Set a responsive value when the layout needs a deliberate change. Prefer intrinsic wrapping when the 45 + content can adapt without a breakpoint.
+9 -20
apps/docs/content/docs/overview/layout.mdx
··· 8 8 9 9 ## Box 10 10 11 - `Box` is a `div` with the supported Sprinkles properties. Use it to space child content. Constrain 12 - its size the same way. Place an item in a flex or grid layout, or hide content at a breakpoint. 11 + `Box` is a `div` with the supported layout properties. Use it to space child content. Constrain its 12 + size the same way. Place an item in a flex or grid layout, or hide content at a breakpoint. 13 13 14 14 ```tsx 15 15 import { Box } from '@luke-ui/react/box'; ··· 24 24 25 25 ## Responsive values 26 26 27 - Every Sprinkles property accepts a direct value or an object keyed by breakpoint. Values cascade 28 - from `xsmall`, so specify only the points where the layout changes. 29 - 30 - ```tsx 31 - <Box 32 - display="flex" 33 - flexDirection={{ xsmall: 'column', medium: 'row' }} 34 - gap={{ xsmall: '300', medium: '600' }} 35 - > 36 - {children} 37 - </Box> 38 - ``` 39 - 40 - The available breakpoints are `xsmall` (base), `small` (640px), `medium` (768px), `large` (1024px), 41 - `xlarge` (1280px), and `xxlarge` (1536px). 27 + Properties passed to `Box` or `createSprinkles` accept responsive values. Specify only the points 28 + where the layout changes. 42 29 43 30 <ExampleBlock src="box/responsive-layout" title="Box — Responsive values" /> 31 + 32 + See [Breakpoints](/overview/breakpoints) for the responsive value API and fixed thresholds. 44 33 45 34 ## Spacing and sizing 46 35 ··· 53 42 54 43 ## Use visual styles elsewhere 55 44 56 - Sprinkles excludes semantic colour, typography, and interaction states. Choose a component API for 57 - those concerns. When a custom element needs a visual token, use `vars` from `@luke-ui/react/theme` 58 - rather than a palette value or component selector. 45 + The layout APIs exclude semantic colour, typography, and interaction states. Choose a component API 46 + for those concerns. When a custom element needs a visual token, use `vars` from 47 + `@luke-ui/react/theme`. 59 48 60 49 ## Continue learning 61 50
+1
apps/docs/content/docs/overview/meta.json
··· 4 4 "getting-started", 5 5 "styling", 6 6 "composition", 7 + "breakpoints", 7 8 "layout", 8 9 "spacing", 9 10 "radius",
+1 -1
apps/docs/content/docs/overview/styling.mdx
··· 77 77 ```tsx 78 78 import { Box } from '@luke-ui/react/box'; 79 79 80 - <Box display="flex" flexDirection={{ xsmall: 'column', medium: 'row' }} gap="400"> 80 + <Box display="flex" flexDirection={{ initial: 'column', medium: 'row' }} gap="400"> 81 81 {children} 82 82 </Box>; 83 83 ```
+1 -1
apps/docs/src/examples/box/responsive-layout.tsx
··· 4 4 5 5 export default function ResponsiveLayout() { 6 6 return ( 7 - <Box display="flex" flexDirection={{ medium: 'row', xsmall: 'column' }} gap="300"> 7 + <Box display="flex" flexDirection={{ initial: 'column', medium: 'row' }} gap="300"> 8 8 <Item /> 9 9 <Item /> 10 10 </Box>
+2 -2
packages/@luke-ui/react/src/box/box.browser.test.tsx
··· 30 30 root.render( 31 31 <Box 32 32 display="flex" 33 - flexDirection={{ medium: 'row', xsmall: 'column' }} 34 - gap={{ medium: '600', xsmall: '200' }} 33 + flexDirection={{ initial: 'column', medium: 'row' }} 34 + gap={{ initial: '200', medium: '600' }} 35 35 > 36 36 <span>First item</span> 37 37 <span>Second item</span>
+3 -3
packages/@luke-ui/react/src/box/box.stories.tsx
··· 21 21 </> 22 22 ), 23 23 display: 'flex', 24 - flexDirection: { medium: 'row', xsmall: 'column' }, 25 - gap: { medium: '600', xsmall: '200' }, 26 - padding: { medium: '600', xsmall: '300' }, 24 + flexDirection: { initial: 'column', medium: 'row' }, 25 + gap: { initial: '200', medium: '600' }, 26 + padding: { initial: '300', medium: '600' }, 27 27 style: { backgroundColor: vars.color.surface.recessed }, 28 28 } satisfies Partial<BoxProps>, 29 29 play: async ({ canvas }) => {
+2 -2
packages/@luke-ui/react/src/box/box.test.ts
··· 4 4 5 5 const boxProps = { 6 6 'aria-label': 'Account summary', 7 - display: { medium: 'flex', xsmall: 'block' }, 7 + display: { initial: 'block', medium: 'flex' }, 8 8 id: 'account-summary', 9 9 onClick: () => undefined, 10 10 padding: '400', ··· 22 22 expectTypeOf(boxProps).toExtend<BoxProps>(); 23 23 expectTypeOf(boxProps.render).toExtend<BoxProps['render']>(); 24 24 expectTypeOf(boxProps.display).toEqualTypeOf<{ 25 + initial: 'block'; 25 26 medium: 'flex'; 26 - xsmall: 'block'; 27 27 }>(); 28 28 });
+2 -2
packages/@luke-ui/react/src/styles/utilities.browser.test.ts
··· 18 18 const element = mount( 19 19 createSprinkles({ 20 20 padding: { 21 + initial: '100', 21 22 large: '400', 22 23 medium: '300', 23 24 small: '200', 24 25 xlarge: '600', 25 - xsmall: '100', 26 26 xxlarge: '800', 27 27 }, 28 28 }), 29 29 ); 30 30 31 31 const breakpoints = [ 32 - { expected: '4px', name: 'xsmall', width: 320 }, 32 + { expected: '4px', name: 'initial', width: 320 }, 33 33 { expected: '8px', name: 'small', width: 640 }, 34 34 { expected: '12px', name: 'medium', width: 768 }, 35 35 { expected: '16px', name: 'large', width: 1024 },
+2 -2
packages/@luke-ui/react/src/styles/utilities.css.ts
··· 4 4 import { layers } from './layers.css.js'; 5 5 6 6 const responsiveConditions = { 7 - xsmall: {}, 7 + initial: {}, 8 8 small: { '@media': 'screen and (width >= 640px)' }, 9 9 medium: { '@media': 'screen and (width >= 768px)' }, 10 10 large: { '@media': 'screen and (width >= 1024px)' }, ··· 30 30 const responsiveProperties = defineProperties({ 31 31 '@layer': layers.utilities, 32 32 conditions: responsiveConditions, 33 - defaultCondition: 'xsmall', 33 + defaultCondition: 'initial', 34 34 dynamicProperties: { 35 35 alignContent: { 36 36 center: 'center',
+3 -3
packages/@luke-ui/react/src/styles/utilities.stories.tsx
··· 67 67 render: () => { 68 68 const responsive = createSprinkles({ 69 69 display: 'flex', 70 - flexDirection: { medium: 'row', xsmall: 'column' }, 71 - gap: { medium: '600', xsmall: '300' }, 72 - padding: { large: '800', xsmall: '300' }, 70 + flexDirection: { initial: 'column', medium: 'row' }, 71 + gap: { initial: '300', medium: '600' }, 72 + padding: { initial: '300', large: '800' }, 73 73 }); 74 74 return ( 75 75 <div {...mergeProps({ style: panelStyle }, responsive)}>
+14 -6
packages/@luke-ui/react/src/styles/utilities.test.ts
··· 3 3 import { createSprinkles } from './utilities.css.js'; 4 4 5 5 const responsiveLayout = { 6 - alignItems: { medium: 'center', xsmall: 'stretch' }, 7 - blockSize: { large: '50vh', xsmall: 'auto' }, 8 - display: { medium: 'grid', xsmall: 'flex' }, 6 + alignItems: { initial: 'stretch', medium: 'center' }, 7 + blockSize: { initial: 'auto', large: '50vh' }, 8 + display: { initial: 'flex', medium: 'grid' }, 9 9 flex: '1 1 auto', 10 10 flexBasis: '12rem', 11 11 flexDirection: 'column', 12 12 flexGrow: '1', 13 13 flexShrink: '0', 14 14 flexWrap: 'wrap', 15 - gap: { medium: '600', xsmall: '200' }, 15 + gap: { initial: '200', medium: '600' }, 16 16 gridColumn: '1 / -1', 17 17 gridColumnEnd: 'span 2', 18 18 gridColumnStart: '2', 19 19 gridRow: 'auto / span 2', 20 - inlineSize: { medium: '50%', xsmall: '100%' }, 20 + inlineSize: { initial: '100%', medium: '50%' }, 21 21 insetBlockStart: '0', 22 22 insetInline: 'var(--luke-space-400)', 23 23 justifyContent: 'space-between', ··· 26 26 maxInlineSize: '64rem', 27 27 minBlockSize: '10rem', 28 28 overflow: 'auto', 29 - padding: { small: '400', xsmall: '300' }, 29 + padding: { initial: '300', small: '400' }, 30 30 placeSelf: 'stretch', 31 31 position: 'sticky', 32 32 rowGap: '1000', ··· 73 73 }, 74 74 } satisfies SprinklesProps; 75 75 76 + const removedXsmallCondition = { 77 + display: { 78 + // @ts-expect-error The responsive base condition is named `initial`. 79 + xsmall: 'block', 80 + }, 81 + } satisfies SprinklesProps; 82 + 76 83 void removedColor; 77 84 void removedBackgroundColor; 78 85 void removedTypography; 79 86 void removedPseudoState; 80 87 void invalidSpaceValue; 81 88 void invalidResponsiveSpaceValue; 89 + void removedXsmallCondition;
+7 -8
apps/docs/content/docs/components/layout/box/index.mdx
··· 3 3 description: A div with responsive Luke UI layout properties. 4 4 --- 5 5 6 - `Box` is a `div` that accepts Luke UI Sprinkles properties. It places content in a layout, adds 7 - token spacing, constrains an element's size, or controls a flex or grid child. Use a normal `div` 8 - when no supported layout property applies. 6 + `Box` is a `div` that accepts Luke UI layout properties. It places content in a layout, adds token 7 + spacing, constrains an element's size, or controls a flex or grid child. Use a normal `div` when no 8 + supported layout property applies. 9 9 10 10 <ExampleBlock src="box/basic" title="Box — Basic" /> 11 11 ··· 27 27 ## Responsive values 28 28 29 29 Pass a direct value when it does not change. Use an object keyed by breakpoint when it does. Values 30 - cascade upward from `xsmall`, so only specify the changes. 30 + cascade upward from `initial`, so only specify the changes. 31 31 32 32 <ExampleBlock src="box/responsive-layout" title="Box — Responsive layout" /> 33 33 34 34 ```tsx 35 35 <Box 36 36 display="flex" 37 - flexDirection={{ xsmall: 'column', medium: 'row' }} 38 - gap={{ xsmall: '200', medium: '600' }} 37 + flexDirection={{ initial: 'column', medium: 'row' }} 38 + gap={{ initial: '200', medium: '600' }} 39 39 > 40 40 {children} 41 41 </Box> 42 42 ``` 43 43 44 - The breakpoints are `xsmall` (base), `small` (640px), `medium` (768px), `large` (1024px), `xlarge` 45 - (1280px), and `xxlarge` (1536px). 44 + See [Breakpoints](/overview/breakpoints) for the available keys and fixed thresholds. 46 45 47 46 ## Spacing values 48 47