Prakash S
Prakash S

Reputation: 2073

How do I get Tailwind's active breakpoint in JavaScript?

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

Answers (13)

Nick Crews
Nick Crews

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

steve_steve
steve_steve

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

Will Squire
Will Squire

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

Neil
Neil

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

pa4080
pa4080

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

Chris Erickson
Chris Erickson

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

lordzouga
lordzouga

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

rozza2058
rozza2058

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

JPDevM
JPDevM

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

Monkey Monk
Monkey Monk

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

anar
anar

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

Adam Eisfeld
Adam Eisfeld

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

Sinandro
Sinandro

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

Related Questions