{"record":{"id":"df8a76156f54d385","repo":"ant-design/ant-design","slug":"screenmax-nextscreenmin-fails-indexab","errorCode":null,"errorMessage":"${screenMax}<=${nextScreenMin} fails : !(${indexableToken[screenMax]}<=${indexableToken[nextScreenMin]})","messagePattern":"(.+?)<=(.+?) fails : !\\((.+?)<=(.+?)\\)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"components/_util/responsiveObserver.ts","lineNumber":59,"sourceCode":"      throw new Error(\n        `${screenMin}<=${screen} fails : !(${indexableToken[screenMin]}<=${indexableToken[screen]})`,\n      );\n    }\n\n    if (i < revBreakpoints.length - 1) {\n      const screenMax = `screen${breakpointUpper}Max`;\n\n      if (!(indexableToken[screen] <= indexableToken[screenMax])) {\n        throw new Error(\n          `${screen}<=${screenMax} fails : !(${indexableToken[screen]}<=${indexableToken[screenMax]})`,\n        );\n      }\n\n      const nextBreakpointUpperMin = revBreakpoints[i + 1].toUpperCase();\n      const nextScreenMin = `screen${nextBreakpointUpperMin}Min`;\n\n      if (!(indexableToken[screenMax] <= indexableToken[nextScreenMin])) {\n        throw new Error(\n          `${screenMax}<=${nextScreenMin} fails : !(${indexableToken[screenMax]}<=${indexableToken[nextScreenMin]})`,\n        );\n      }\n    }\n  });\n  return token;\n};\n\nexport const matchScreen = (screens: ScreenMap, screenSizes?: ScreenSizeMap) => {\n  if (!screenSizes) {\n    return;\n  }\n  for (const breakpoint of responsiveArray) {\n    if (screens[breakpoint] && screenSizes?.[breakpoint] !== undefined) {\n      return screenSizes[breakpoint];\n    }\n  }\n};","sourceCodeStart":41,"sourceCodeEnd":77,"githubUrl":"https://github.com/ant-design/ant-design/blob/6455d91fabd6e58f3e89014105640e8ceb9ff502/components/_util/responsiveObserver.ts#L41-L77","documentation":"validateBreakpoints() asserts that consecutive breakpoints chain correctly: screenXXMax <= nextScreenYYMin, where 'next' is the next entry in the reversed breakpoint array (i.e. the next smaller breakpoint). By default screenXXMax = nextScreen - 1 and nextScreenMin = nextScreen, so the invariant holds (575 <= 576). This is the third check in the loop (line 58), skipped for the last breakpoint. When it fails, one breakpoint's ceiling overlaps or exceeds the next breakpoint's floor, creating a gap or contradiction in the media-query lattice that responsive observers depend on.","triggerScenarios":"Fires when a ConfigProvider theme.token override makes screenXXMax > nextScreenYYMin. Typical: { screenXS: 1000, screenXSMin: 1000, screenXSMax: 1010 } where screenSMMin stays at 576, producing screenXSMax<=screenSMMin fails : !(1010<=576). Also triggers when increasing any screenXXMax beyond the next breakpoint's Min without also raising that next breakpoint.","commonSituations":"Occurs when a developer overrides one breakpoint's Max (or all three values of one tier) but leaves adjacent breakpoints at defaults — the raised ceiling spills past the neighbor's floor. Also seen when porting a design-system spec that defines non-contiguous or overlapping breakpoints, or when incrementally adding token overrides without re-checking the full chain after each addition.","solutions":["Read the error message for the exact pair, e.g. screenXSMax<=screenSMMin fails : !(1010<=576). Raise the second token (nextScreenMin) above the first (screenMax), or lower screenMax.","When you increase screenXXMax, also increase the next breakpoint's screenYY / screenYYMin so that screenXXMax < screenYYMin.","Override breakpoint tiers in contiguous groups rather than one tier at a time, so every boundary is consistent.","Validate the full chain xs→xxxl before passing tokens to ConfigProvider: screenXSMax < screenSMMin, screenSMMax < screenMDMin, and so on for all six boundaries."],"exampleFix":"// before — screenXSMax (1010) exceeds screenSMMin (576)\n<ConfigProvider theme={{ token: { screenXS: 1000, screenXSMin: 1000, screenXSMax: 1010 } }}>\n  <App />\n</ConfigProvider>\n\n// after — raise the adjacent breakpoint to preserve the chain\n<ConfigProvider theme={{ token: { screenXS: 1000, screenXSMin: 1000, screenXSMax: 1010, screenSM: 1024, screenSMMin: 1024 } }}>\n  <App />\n</ConfigProvider>","handlingStrategy":"validation","validationCode":"import { responsiveArray } from 'antd/es/_util/responsiveObserver';\n\n// Call before passing tokens to ConfigProvider\nfunction assertBreakpointChainValid(token: Record<string, any>): void {\n  const rev = [...responsiveArray].reverse(); // xxxl, xxl, xl, lg, md, sm, xs\n  for (let i = 0; i < rev.length - 1; i++) {\n    const upper = rev[i].toUpperCase();\n    const nextUpper = rev[i + 1].toUpperCase();\n    const max = token[`screen${upper}Max`];\n    const nextMin = token[`screen${nextUpper}Min`];\n    if (typeof max === 'number' && typeof nextMin === 'number' && max > nextMin) {\n      throw new Error(\n        `Invalid token chain: screen${upper}Max (${max}) > screen${nextUpper}Min (${nextMin}). Raise the next breakpoint or lower screen${upper}Max.`,\n      );\n    }\n  }\n}\n\n// Usage:\n// const myToken = { screenXS: 1000, screenXSMin: 1000, screenXSMax: 1010, screenSM: 1024, screenSMMin: 1024 };\n// assertBreakpointChainValid(myToken);\n// <ConfigProvider theme={{ token: myToken }}>...","typeGuard":"import type { AliasToken } from 'antd/es/theme/interface/alias';\nimport { responsiveArray } from 'antd/es/_util/responsiveObserver';\n\nfunction hasValidBreakpointChain(token: Partial<AliasToken>): boolean {\n  const rev = [...responsiveArray].reverse();\n  for (let i = 0; i < rev.length - 1; i++) {\n    const upper = rev[i].toUpperCase();\n    const nextUpper = rev[i + 1].toUpperCase();\n    const max = token[`screen${upper}Max` as keyof AliasToken] as number | undefined;\n    const nextMin = token[`screen${nextUpper}Min` as keyof AliasToken] as number | undefined;\n    if (max === undefined || nextMin === undefined) continue;\n    if (max > nextMin) return false;\n  }\n  return true;\n}","tryCatchPattern":"// Throws during React render — wrap in an Error Boundary, not a try/catch:\nimport React from 'react';\n\nclass BreakpointErrorBoundary extends React.Component<\n  { children: React.ReactNode; fallback?: React.ReactNode },\n  { hasError: boolean }\n> {\n  state = { hasError: false };\n  static getDerivedStateFromError() {\n    return { hasError: true };\n  }\n  componentDidCatch(err: Error) {\n    if (err.message.includes('fails : !(')) {\n      console.error('Breakpoint chain validation failed:', err.message);\n    }\n  }\n  render() {\n    return this.state.hasError ? (this.props.fallback ?? null) : this.props.children;\n  }\n}\n// This only masks the crash; the responsive component will not render until\n// the token chain is fixed.","preventionTips":["When raising any breakpoint tier, immediately check the boundary against both neighbors: screenXXMax must stay below the next smaller breakpoint's Min.","Override breakpoint tiers in contiguous cascading groups — if you raise screenXL, also raise screenXXL (and its Max), and check that screenXLMax < screenXXLMin.","Validate the entire xs→xxxl chain with a helper before passing tokens to ConfigProvider; check all six boundaries in one pass.","Centralize breakpoint config in one typed, validated constant imported throughout the app.","When translating from another design system's breakpoints, map the full ordered set at once and verify no overlaps or gaps exist."],"tags":["theme","design-token","responsive","breakpoints","config-provider","validation"],"backgroundTag":null,"analyzedSha":"6455d91fabd6e58f3e89014105640e8ceb9ff502","analyzedAt":"2026-08-12T05:50:11.441Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}