Reputation: 2073
I am building Tailwind with config file and including it in a React project.
I would like to get the active breakpoint value in JavaScript/React. How can I achieve the same?
<div class="block sm:hidden md:hidden lg:hidden xl:hidden">al</div>
<div class="hidden sm:block md:hidden lg:hidden xl:hidden">sm</div>
<div class="hidden sm:hidden md:block lg:hidden xl:hidden">md</div>
<div class="hidden sm:hidden md:hidden lg:block xl:hidden">lg</div>
<div class="hidden sm:hidden md:hidden lg:hidden xl:block">xl</div>
</div>
The above shows the active breakpoints. But how do I get the same in JS without including any of the above markup?
Upvotes: 43
Views: 54627
Reputation: 916
My version of this is
import resolveConfig from "tailwindcss/resolveConfig";
import tailwindConfig from "@/tailwind.config";
import { Config } from "tailwindcss/types/config";
import {useMemo, useEffect, useState, use} from "react";
export const config = resolveConfig(tailwindConfig as unknown as Config);
const breakpoints = config.theme.screens;
type BreakpointKey = keyof typeof breakpoints;
export function useBreakpoint<K extends BreakpointKey>(breakpointKey: K) {
// breakpointValueText is something like "640px"
const breakpointValueText = breakpoints[breakpointKey];
const breakpointValueInt = Number(breakpointValueText.replace(/[^0-9]/g, ""));
const isBelow = useIsBelowWidth(breakpointValueInt);
const capitalizedKey = breakpointKey[0].toUpperCase() + breakpointKey.substring(1);
const result = {
[`text${capitalizedKey}`]: breakpointValueText,
[`number${capitalizedKey}`]: breakpointValueInt,
[`isBelow${capitalizedKey}`]: isBelow,
[`isAbove${capitalizedKey}`]: isBelow === undefined ? undefined : !isBelow,
} as (
Record<`text${Capitalize<K>}`, string> &
Record<`number${Capitalize<K>}`, number> &
Record<`isBelow${Capitalize<K>}`, boolean | undefined> &
Record<`isAbove${Capitalize<K>}`, boolean | undefined>
);
return useMemo(() => result, [breakpointValueInt, isBelow]);
}
/**
* A React hook that returns a boolean indicating whether the window width is below a specified value.
* The hook automatically updates when the window is resized.
*
* Based on: https://dev.to/musselmanth/re-rendering-react-components-at-breakpoint-window-resizes-a-better-way-4343
*
* @param innerWidth - The width threshold in pixels to check against
* @returns {boolean | undefined} - A boolean indicating whether the window width is
* below the specified value, or undefined if the window object is not available.
*
* @example
* ```tsx
* const isMobile = useIsBelowWidth(768);
* ```
*/
export function useIsBelowWidth(innerWidth: number) : boolean | undefined {
const [isBelowWidth, setIsBelowWidth] = useState<boolean | undefined>(undefined);
useEffect(() => {
const windowResizeHandler = () => {
setIsBelowWidth(window.innerWidth <= innerWidth);
};
windowResizeHandler();
window.addEventListener('resize', windowResizeHandler);
return () => window.removeEventListener('resize', windowResizeHandler);
}, [innerWidth]);
return isBelowWidth;
};
use as:
const { text2xl, number2xl, isBelow2xl, isAbove2xl } = useBreakpoint('2xl');
console.log({text2xl, number2xl, isBelow2xl, isAbove2xl});
// {text2xl: '1536px', number2xl: 1536, isBelow2xl: true, isAbove2xl: false}
I had to use the awkward naming convention of text${capitalizedKey}
because the breakpoint 2xl
starts with a number, so it can't start an identifier, eg 2xlText
would be illegal.
Upvotes: 0
Reputation: 9
Maybe a nice addon/alternative to @Adam Eisfelds answer:
function isBreakpoint(alias) {
return $('.device-' + alias).is(':visible')
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.1/jquery.min.js"></script>
<div class="device-sm hidden sm:block md:hidden"></div>
<div class="device-md hidden md:block lg:hidden"></div>
<div class="device-lg hidden lg:block xl:hidden"></div>
<div class="device-xl hidden xl:block 2xl:hidden"></div>
<div class="device-2xl hidden 2xl:block"></div>
Just call for example if isBreakpoint('md')
somewhere to ask if its the breakpoint.
Also I modified it into isMinBreakpoint
:
function isMinBreakpoint(alias) {
return $('.device-' + alias).is(':visible');
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.1/jquery.min.js"></script>
<div class="device-sm hidden sm:block"></div>
<div class="device-md hidden md:block"></div>
<div class="device-lg hidden lg:block"></div>
<div class="device-xl hidden xl:block"></div>
<div class="device-2xl hidden 2xl:block"></div>
So just call if isMinBreakpoint('md')
to check if this is at least MD-breakpoint.
Upvotes: 0
Reputation: 6595
I quite like the simplicity of:
import { useMediaQuery } from 'react-responsive'
import resolveConfig from 'tailwindcss/resolveConfig'
import tailwindConfig from '../../tailwind.config’ // change to your `tailwind.config` path
const config = resolveConfig(tailwindConfig)
const breakpoints = config.theme.screens
/**
* Returns `true` if screen size matches the
* `breakpoint`.
*/
export const useBreakpoint = (breakpoint: keyof typeof breakpoints) => {
const breakpointQuery = breakpoints[breakpoint]
return useMediaQuery({ query: `(min-width: ${breakpointQuery})` })
}
With usage:
const mdUp = useBreakpoint('md')
Upvotes: 2
Reputation: 329
I couldn't edit @MonkeyMonk's answer so adding modifications to his here - my favorite solution:
export function getCurrentBreakpoints() {
if (!window || typeof window === 'undefined') {
return null;
}
let breakpoints = Object.keys(theme.screens).map((key) => Number(theme.screens[key]?.replace(/\D/g, '') || 1e6)).reverse();
let keys = Object.keys(theme.screens).reverse();
return keys[breakpoints.findIndex((screenSize: number) => window?.innerWidth > screenSize)] ?? 'sm';
}
Upvotes: 0
Reputation: 951
I've combined the other solutions to achieve what I need.
/**
* @desc The 'useBreakpoint()' hook is used to get the current
* screen breakpoint based on the TailwindCSS config.
*
* @usage
* import { useBreakpoint } from "@/hooks/useBreakpoint";
*
* const { isAboveSm, isBelowSm, sm } = useBreakpoint("sm");
* console.log({ isAboveSm, isBelowSm, sm });
*
* const { isAboveMd } = useBreakpoint("md");
* const { isAboveLg } = useBreakpoint("lg");
* const { isAbove2Xl } = useBreakpoint("2xl");
* console.log({ isAboveMd, isAboveLg, isAbove2Xl });
*
* @see https://stackoverflow.com/a/76630444/6543935
* @requirements npm install react-responsive
*/
import { useMediaQuery } from "react-responsive";
import resolveConfig from "tailwindcss/resolveConfig";
import { Config, ScreensConfig } from "tailwindcss/types/config";
import tailwindConfig from "@/tailwind.config"; // Your tailwind config
const fullConfig = resolveConfig(tailwindConfig as unknown as Config);
const breakpoints = fullConfig?.theme?.screens || {
xs: "480px",
sm: "640px",
md: "768px",
lg: "1024px",
xl: "1280px",
};
export function useBreakpoint<K extends string>(breakpointKey: K) {
const breakpointValue = breakpoints[breakpointKey as keyof typeof breakpoints];
const bool = useMediaQuery({
query: `(max-width: ${breakpointValue})`,
});
const capitalizedKey = breakpointKey[0].toUpperCase() + breakpointKey.substring(1);
type KeyAbove = `isAbove${Capitalize<K>}`;
type KeyBelow = `isBelow${Capitalize<K>}`;
return {
[breakpointKey]: Number(String(breakpointValue).replace(/[^0-9]/g, "")),
[`isAbove${capitalizedKey}`]: !bool,
[`isBelow${capitalizedKey}`]: bool,
} as Record<K, number> & Record<KeyAbove | KeyBelow, boolean>;
}
Here is an example of usage within Next.js 13 with App router:
"use client";
import React, { useEffect } from "react";
import { useBreakpoint } from "@/hooks/useBreakpoint";
const Nav: React.FC = () => {
const { isAboveSm, isBelowSm, sm } = useBreakpoint("sm");
useEffect(() => {
console.log({ isAboveSm, isBelowSm, sm });
});
return (
<> ... </>
);
};
Console output (on screen width larger than 640px):
{ isAboveSm: true, isBelowSm: false, sm: 640 }
In case Next.js complains the Server side rendering doesn't match with the Client side, you can use useLayoutEffect()
hook:
const [isBwXs, setIsBwXs] = React.useState<boolean>(false);
const { isBelowXs } = useBreakpoint("xs");
useLayoutEffect(() => {
setIsBwXs(isBelowXs);
}, [isBelowXs]);
Note: Depending on your configuration you may need to change the tailwind.config
file from module.exports = {...}
to something like:
const tailwindConfig: import("tailwindcss").Config = {...};
export default tailwindConfig;
Upvotes: 19
Reputation: 161
If you don't want to import any additional dependencies, you can just do something like this:
import resolveConfig from "tailwindcss/resolveConfig";
import overrides from "../tailwind.config";
import { useEffect, useState } from "react";
const config = resolveConfig(overrides);
export const useIsBreakpointActive = (
breakpoint: "sm" | "md" | "lg" | "xl" | "2xl"
): boolean => {
const width = config.theme.screens[breakpoint];
const widthPx = parseInt(width);
const isBreakpointActive = window.innerWidth >= widthPx;
const [wasBreakpointActive, setWasBreakpointActive] =
useState(isBreakpointActive);
// if the breakpoint argument changes, immediately set state
// and re-render
if (wasBreakpointActive !== isBreakpointActive) {
setWasBreakpointActive(isBreakpointActive);
}
useEffect(functi() => {
const handleResize = () =>
setWasBreakpointActive(window.innerWidth >= widthPx);
window.addEventListener("resize", handleResize);
return () => window.removeEventListener("resize", handleResize);
}, []);
return isBreakpointActive;
};
Upvotes: 2
Reputation: 684
Using Nuxt3, I adapted the accepted answer to js.
import tailwindConfig from '~/tailwind.config';
import resolveConfig from 'tailwindcss/resolveConfig';
const { theme: { screens } } = resolveConfig(tailwindConfig);
const getActiveBreakpoint = () => {
/* Sort the breakpoints based on their dimensions in descending order */
const sorted = Object.entries(screens).sort((x, y) => parseInt(y[1]) - parseInt(x[1]));
/* Find the first instance where the current width is higher or equal to a breakpoint */
const bp = sorted.find((s) => window.innerWidth >= parseInt(s[1]));
/* if no breakpoint is found, it is a mobile screen */
if (!bp) return "mb"
else return bp[0]
}
remember to call getActiveBreakpoint()
in onMounted()
.
Upvotes: 0
Reputation: 678
From the tailwind docs, you can import your config from the tailwindcss
node module:
import resolveConfig from 'tailwindcss/resolveConfig'
import tailwindConfig from './tailwind.config.js'
const fullConfig = resolveConfig(tailwindConfig)
fullConfig.theme.width[4]
// => '1rem'
fullConfig.theme.screens.md
// => '768px'
fullConfig.theme.boxShadow['2xl']
// => '0 25px 50px -12px rgba(0, 0, 0, 0.25)'
As you can see above, you can get your breakpoints by referencing fullConfig.theme.screens.{breakpoint}
. You should be able to compare this to your current screen width using javascript.
Find the official tailwind description here.
Upvotes: 51
Reputation: 121
I can resolve this in React like this:
{/* MOBILE FIRST */}
<div className="sm:hidden">
<Component breakpoint="mobile" />
</div>
{/* SMALL */}
<div className="hidden sm:block md:hidden">
<Component breakpoint="sm" />
</div>
{/* MEDIUM */}
<div className="hidden md:block lg:hidden">
<Component breakpoint="md" />
</div>
{/* LARGE */}
<div className="hidden lg:block xl:hidden">
<Component breakpoint="xl" />
</div>
{/* EXTRA LARGE */}
<div className="hidden xl:block 2xl:hidden">
<Component breakpoint="xl" />
</div>
and in my Component.jsx
import React from 'react'
const Component = (prop) => {
const { breakpoint } = prop;
return (
<div>{breakpoint}</div>
)
}
export default Component
Upvotes: 3
Reputation: 990
For this purpose I use this peace of code :
import { theme } from '../../tailwind.config';
export function getCurrentBreakpoints() {
return Object.keys(theme.screens).find((key) => window.innerWidth > theme.screens[key]);
}
Upvotes: 3
Reputation: 625
Here is a little hook for those using TypeScript 4.5+. It's based on the useMediaQuery
hook from the react-responsive
package. Modify it as you please!
import { useMediaQuery } from 'react-responsive';
import { theme } from '../../tailwind.config'; // Your tailwind config
const breakpoints = theme.screens;
type BreakpointKey = keyof typeof breakpoints;
export function useBreakpoint<K extends BreakpointKey>(breakpointKey: K) {
const bool = useMediaQuery({
query: `(min-width: ${breakpoints[breakpointKey]})`,
});
const capitalizedKey = breakpointKey[0].toUpperCase() + breakpointKey.substring(1);
type Key = `is${Capitalize<K>}`;
return {
[`is${capitalizedKey}`]: bool,
} as Record<Key, boolean>;
}
Inside your component, use it like this
const { isSm } = useBreakpoint('sm');
const { isMd } = useBreakpoint('md');
const { isLg } = useBreakpoint('lg');
return (
<div>
{isSm && (
{/* Visible for sm: (min-width: 640px) */}
<div>Content</div>
)}
{isMd && (
{/* Visible for md: (min-width: 768px) */}
<div>Content</div>
)}
</div>
);
Upvotes: 20
Reputation: 1474
If anyone is looking for an approach that doesn't use tailwind's config, you can achieve this by using tailwind's breakpoint system to control the visibility of several 0x0 divs, and test for the visibility of those divs to determine the current active breakpoint.
For example, you might embed several divs of size 0 in your body like so:
<div id="breakpoint-sm" class="hidden sm:block md:hidden lg:hidden xl:hidden 2xl:hidden w-0 h-0"></div>
<div id="breakpoint-md" class="hidden sm:hidden md:block lg:hidden xl:hidden 2xl:hidden w-0 h-0"></div>
<div id="breakpoint-lg" class="hidden sm:hidden md:hidden lg:block xl:hidden 2xl:hidden w-0 h-0"></div>
<div id="breakpoint-xl" class="hidden sm:hidden md:hidden lg:hidden xl:block 2xl:hidden w-0 h-0"></div>
<div id="breakpoint-2xl" class="hidden sm:hidden md:hidden lg:hidden xl:hidden 2xl:block w-0 h-0"></div>
Then, you can write a function that looks for these elements and checks if they are visible via each element's offsetParent property:
const getCurrentBreakpoint = (): string => {
const breakpointUnknown: string = 'unknown';
const breakpointSM: string | null = document.getElementById('breakpoint-sm')?.offsetParent === null ? null : 'sm';
const breakpointMD: string | null = document.getElementById('breakpoint-md')?.offsetParent === null ? null : 'md';
const breakpointLG: string | null = document.getElementById('breakpoint-lg')?.offsetParent === null ? null : 'lg';
const breakpointXL: string | null = document.getElementById('breakpoint-xl')?.offsetParent === null ? null : 'xl';
const breakpoint2XL: string | null = document.getElementById('breakpoint-2xl')?.offsetParent === null ? null : '2xl';
const breakpoint = breakpointSM ?? breakpointMD ?? breakpointLG ?? breakpointXL ?? breakpoint2XL ?? breakpointUnknown;
return breakpoint;
};
Now you can test for the current breakpoint string to perform some logic:
const breakpoint = getCurrentBreakpoint();
const desktopBreakpoints: string[] = ['sm', 'md', 'lg', 'xl'];
if (desktopBreakpoints.includes(breakpoint)) {
// On Desktop (in Tailwind's eyes)
} else {
// On Mobile (in Tailwind's eyes)
}
You'll need to ensure whatever breakpoints you have Tailwind using are both applied to a 0x0 div somewhere in your document that you can obtain, and to check for those breakpoints in your getCurrentBreakpoint() function. But this gets the job done without needing to inspect Tailwind's config and uses Tailwind's actual breakpoint system to determine which is currently active.
Upvotes: 7
Reputation: 2686
Here's what I wrote in Typescript
that returns the current breakpoint based on device width. You can place it in a standalone file and import the methods whenever needed in any file:
import resolveConfig from 'tailwindcss/resolveConfig';
import tailwindConfig from './tailwind.config'; // Fix the path
const fullConfig = resolveConfig(tailwindConfig);
export const getBreakpointValue = (value: string): number =>
+fullConfig.theme.screens[value].slice(
0,
fullConfig.theme.screens[value].indexOf('px')
);
export const getCurrentBreakpoint = (): string => {
let currentBreakpoint: string;
let biggestBreakpointValue = 0;
for (const breakpoint of Object.keys(fullConfig.theme.screens)) {
const breakpointValue = getBreakpointValue(breakpoint);
if (
breakpointValue > biggestBreakpointValue &&
window.innerWidth >= breakpointValue
) {
biggestBreakpointValue = breakpointValue;
currentBreakpoint = breakpoint;
}
}
return currentBreakpoint;
};
Edit:
in newer Typescript versions you have to add these two parameters to tsconfig.json
under compilerOptions
in order to be able import js files:
"compilerOptions": {
"allowJs": true,
"allowsyntheticdefaultimports": true
}
Also, if you are on Angular and get the error that process
is not defined, you have to add these lines to the end of your polyfills.ts
file (you have to install the process
package of course):
import * as process from 'process';
window['process'] = process;
Upvotes: 13