DEV Community

Cover image for Breakpoints reactivity with Tailwind and VueJS
florent giraud
florent giraud

Posted on

Breakpoints reactivity with Tailwind and VueJS

Hello VUEJS lovers ! ❤️

TL:TR

Before Tailwind I used Vuetify. Vuetify is an incredible css framework!

But 🤔

I faced some issues with Vuetify:

  • "Forced" to use Vuetify components
  • Making changes in some cases could become really difficult....

Since then, I've discovered Tailwind!

Tailwind is what we call a "Css utility" which allows us to have much more modularity.

With that being said.

What I liked and didn't find with Tailwind are the Breakpoints Reactivity connected to VueJS. It let you change css classes dynamically or styles etc...

<template>
  <div :class="{classCustom: breakpoints.xs}" ></div>
</template>
Enter fullscreen mode Exit fullscreen mode

So I decided to do it myself.

Let's go connect Tailwind breakpoints reactivity with VueJS 😇

First, I was inspired by what Vuetify was doing, as integrate the breakpoints logic via Plugins. It is handy since it adds some "globals property" => The Breakpoints.

Yeah but it's "global". I don't like this aspect of globals, that mean you have to take that and ok... If I don't want them inside my component and save amount of code after transpilation.
On top of that, the customisation of the properties name etc are almost impossible.

So I decided to take advantage of the Vue Observable.

import Vue from 'vue'

const screens = {
  sm: 640,
  md: 768,
  lg: 1024,
  xl: 1280
}

const sm = val => val >= screens.sm && val <= screens.md
const md = val => val >= screens.md && val <= screens.lg
const lg = val => val >= screens.lg && val <= screens.xl
const xl = val => val >= screens.xl

const getBreakpoint = w => {
  if (sm(w)) return 'sm'
  else if (md(w)) return 'md'
  else if (lg(w)) return 'lg'
  else if (xl(w)) return 'xl'
  else return 'all'
}
const debounce = function(func, wait) {
  var timeout
  return () => {
    const later = function() {
      timeout = null
    }
    const callNow = !timeout
    clearTimeout(timeout)
    timeout = setTimeout(later, wait)
    if (callNow) func()
  }
}

const breakpoints = Vue.observable({
  w: window.innerWidth,
  h: window.innerHeight,
  is: getBreakpoint(window.innerWidth)
})

window.addEventListener(
  'resize',
  debounce(() => {
    breakpoints.w = window.innerWidth
    breakpoints.h = window.innerHeight
    breakpoints.is = getBreakpoint(window.innerWidth)
  }, 200),
  false
)

export default breakpoints
Enter fullscreen mode Exit fullscreen mode

And you can now use them just simply like this...

<template>
  <div>{{ breakpoints.is }} {{ breakpoints.w }} {{ breakpoints.h }} </div>
</template>
<script>
import breakpoints from '@/plugins/breakpoints'

export default {
  name: 'Component',
  data: () => {
    return {
      breakpoints
    }
  }
}
</script>
Enter fullscreen mode Exit fullscreen mode

And That's it! Since we are using Vue.observable, vue will automatically put those data reactive.

ps: It work's well with functional components!

<script>
import breakpoints from '@/plugins/breakpoints.js'

export default {
  name: 'TitleXL',
  functional: true,
  props: {
    text: {
      type: String,
      default: ''
    }
  },
  render: (h, { data, props }) => {
    const textW = breakpoints.mdAndUp ? 'text-5xl' : 'text-3xl'

    return (
      <div class={textW} {...data}>
        {props.text}
      </div>
    )
  }
}
</script>

Enter fullscreen mode Exit fullscreen mode

Now you don't get data where you don't need it to be anymore ❤️

As you know, any code can be improved. If you have any suggestion,
Feel free to contact me or let a comment or just like the article :)

my twitter: https://twitter.com/giraud_florent
my linkedin: https://www.linkedin.com/in/fgiraud42/

Top comments (5)

Collapse
 
roelofr profile image
Roelof • Edited

Thanks for this! I was fairly new to observables so this helped out!

I had one remark though: why the x >= current && x < next statements, when you can just start at the largest breakpoint and descend, skipping the upper bound check on the lower breakpoints.

const getBreakpoint = width => {
  if (width >= screens.xl) return 'xl'
  if (width >= screens.lg) return 'lg'
  if (width >= screens.md) return 'md'
  if (width >= screens.sm) return 'sm'
  return 'all'
}
Enter fullscreen mode Exit fullscreen mode

the result would be the same, but it's a lot shorter and a lot easier to wrap your head around.

Collapse
 
boussadjra profile image
Brahim

Thank you very much, I used your code to create a composable function :

import { onMounted, reactive } from "vue"

const screens = {
    xs: 320,
    sm: 640,
    md: 768,
    lg: 1024,
    xl: 1280
}


const xs = (val: number) => val >= screens.xs && val <= screens.sm
const sm = (val: number) => val >= screens.sm && val <= screens.md
const md = (val: number) => val >= screens.md && val <= screens.lg
const lg = (val: number) => val >= screens.lg && val <= screens.xl
const xl = (val: number) => val >= screens.xl

const getBreakpoint = (w: number) => {
    if (xs(w)) return 'xs'
    else if (sm(w)) return 'sm'
    else if (md(w)) return 'md'
    else if (lg(w)) return 'lg'
    else if (xl(w)) return 'xl'
    else return 'all'
}


const useBreakpoint = () => {
    const breakpoints = reactive({ w: 0, h: 0, is: "xs" })

    onMounted(() => {
        window.addEventListener(
            'resize',
            () => {
                breakpoints.w = window.innerWidth
                breakpoints.h = window.innerHeight
                breakpoints.is = getBreakpoint(window.innerWidth)
            }

        )


    })

    return {
        breakpoints
    }
}

export default useBreakpoint
Enter fullscreen mode Exit fullscreen mode
Collapse
 
charlesokwuagwu profile image
Charles • Edited

Slight refactor of your implemetation... to ensure the breakpoint gets calculated on load, if there is no resize.

import { onMounted, reactive } from 'vue'

const screens = {
  xs: 320,
  sm: 640,
  md: 768,
  lg: 1024,
  xl: 1280
}

const breakpoints = reactive({ w: 0, h: 0, is: 'xs' })

const xs = (val: number) => val >= screens.xs && val < screens.sm
const sm = (val: number) => val >= screens.sm && val < screens.md
const md = (val: number) => val >= screens.md && val < screens.lg
const lg = (val: number) => val >= screens.lg && val < screens.xl
const xl = (val: number) => val >= screens.xl

const getBreakpoint = (w: number) => {
  if (xs(w)) return 'xs'
  else if (sm(w)) return 'sm'
  else if (md(w)) return 'md'
  else if (lg(w)) return 'lg'
  else if (xl(w)) return 'xl'
  else return 'all'
}

const setBreakpoint = () => {
  breakpoints.w = window.innerWidth
  breakpoints.h = window.innerHeight
  breakpoints.is = getBreakpoint(window.innerWidth)
}

const useBreakpoint = () => {
  onMounted(() => {
    setBreakpoint()
    window.addEventListener('resize', () => {
      setBreakpoint()
    })
  })

  return {
    breakpoints
  }
}

export default useBreakpoint
Enter fullscreen mode Exit fullscreen mode
Collapse
 
tbredin profile image
T Bredin • Edited

Great utility. I've extended this to reference my breakpoints from the tailwind config so that they can be managed all in one place. My breakpoints are stored (as em's) in the tailwind config like so:

module.exports = {
  theme: {
    screens: {
      sm: '30em',
      md: '48em',
      lg: '64em',
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

And my breakpoint utility imports and converts them to pixels so we can make use of them, just modify the const screens variable to use the imported values like so:

import tailwindConfig from '@/tailwind.config.js';

const getPxBreakpoint = (val) => {
  return parseFloat(val) * 16;
};

export const screens = {
  sm: getPxBreakpoint(tailwindConfig.theme.screens.sm),
  md: getPxBreakpoint(tailwindConfig.theme.screens.md),
  lg: getPxBreakpoint(tailwindConfig.theme.screens.lg),
};
Enter fullscreen mode Exit fullscreen mode
Collapse
 
flozero profile image
florent giraud

glad it helps you :)