When making a component-based, front-end infrastructure, one of many largest ache factors I’ve personally encountered is making elements which might be each reusable and responsive when there are nested elements inside elements.
Take the next “name to motion” (<CTA />) element, for instance:
On smaller gadgets we wish it to appear to be this:
That is easy sufficient with primary media queries. If we’re utilizing flexbox, a media question can change the flex path and makes the button go the complete width. However we run into an issue once we begin nesting different elements in there. For instance, say we’re utilizing a element for the button and it already has a prop that makes it full-width. We are literally duplicating the button’s styling when making use of a media question to the father or mother element. The nested button is already able to dealing with it!
It is a small instance and it wouldn’t be that unhealthy of an issue, however for different situations it might trigger a whole lot of duplicated code to duplicate the styling. What if sooner or later we needed to vary one thing about how full-width buttons are styled? We’d have to undergo and alter it in all these totally different locations. We must always be capable to change it within the button element and have that replace all over the place.
Wouldn’t or not it’s good if we might transfer away from media queries and have extra management of the styling? We ought to be utilizing a element’s present props and be capable to move totally different values based mostly on the display screen width.
Properly, I’ve a method to try this and can present you ways I did it.
I’m conscious that container queries can remedy a whole lot of these points, however it’s nonetheless in early days and doesn’t remedy the difficulty with passing quite a lot of props based mostly on display screen width.
Monitoring the window width
First, we have to monitor the present width of the web page and set a breakpoint. This may be performed with any front-end framework, however I’m utilizing a Vue composable right here as to reveal the concept:
// composables/useBreakpoints.js
import { readonly, ref } from “vue”;
const bps = ref({ xs: 0, sm: 1, md: 2, lg: 3, xl: 4 })
const currentBreakpoint = ref(bps.xl);
export default () => {
const updateBreakpoint = () => {
const windowWidth = window.innerWidth;
if(windowWidth >= 1200) {
currentBreakpoint.worth = bps.xl
} else if(windowWidth >= 992) {
currentBreakpoint.worth = bps.lg
} else if(windowWidth >= 768) {
currentBreakpoint.worth = bps.md
} else if(windowWidth >= 576) {
currentBreakpoint.worth = bps.sm
} else {
currentBreakpoint.worth = bps.xs
}
}
return {
currentBreakpoint: readonly(currentBreakpoint),
bps: readonly(bps),
updateBreakpoint,
};
};
The rationale we’re utilizing numbers for the currentBreakpoint object will turn out to be clear later.
Now we are able to pay attention for window resize occasions and replace the present breakpoint utilizing the composable in the principle App.vue file:
// App.vue
<script>
import useBreakpoints from “@/composables/useBreakpoints”;
import { onMounted, onUnmounted } from ‘vue’
export default {
identify: ‘App’,
setup() {
const { updateBreakpoint } = useBreakpoints()
onMounted(() => {
updateBreakpoint();
window.addEventListener(‘resize’, updateBreakpoint)
})
onUnmounted(() => {
window.removeEventListener(‘resize’, updateBreakpoint)
})
}
}
</script>
We in all probability need this to be debounced, however I’m preserving issues easy for brevity.
Styling elements
We will replace the <CTA /> element to just accept a brand new prop for the way it ought to be styled:
// CTA.vue
props: {
displayMode: {
sort: String,
default: “default”
}
}
The naming right here is completely arbitrary. You should utilize no matter names you’d like for every of the element modes.
We will then use this prop to vary the mode based mostly on the present breakpoint:
<CTA :display-mode=”currentBreakpoint > bps.md ? ‘default’ : ‘compact'” />
You possibly can see now why we’re utilizing a quantity to symbolize the present breakpoint — it’s so the right mode may be utilized to all breakpoints beneath or above a sure quantity.
We will then use this within the CTA element to fashion in line with the mode handed via:
// elements/CTA.vue
<template>
<div class=”cta” :class=”displayMode”>
<div class=”cta-content”>
<h5>title</h5>
<p>description</p>
</div>
<Btn :block=”displayMode === ‘compact'”>Proceed</Btn>
</div>
</template>
<script>
import Btn from “@/elements/ui/Btn”;
export default {
identify: “CTA”,
elements: { Btn },
props: {
displayMode: {
sort: String,
default: “default”
},
}
}
</script>
<fashion scoped lang=”scss”>
.cta {
show: flex;
align-items: middle;
.cta-content {
margin-right: 2rem;
}
&.compact {
flex-direction: column;
.cta-content {
margin-right: 0;
margin-bottom: 2rem;
}
}
}
</fashion>
Already, we now have eliminated the necessity for media queries! You possibly can see this in motion on a demo web page I created.
Admittedly, this will appear to be a prolonged course of for one thing so easy. However when utilized to a number of elements, this method can massively enhance the consistency and stability of the UI whereas decreasing the full quantity of code we have to write. This fashion of utilizing JavaScript and CSS courses to regulate the responsive styling additionally has one other profit…
Extensible performance for nested elements
There have been situations the place I’ve wanted to revert again to a earlier breakpoint for a element. For instance, if it takes up 50% of the display screen, I need it displayed within the small mode. However at a sure display screen measurement, it turns into full-width. In different phrases, the mode ought to change by some means when there’s a resize occasion.
I’ve additionally been in conditions the place the identical element is utilized in totally different modes on totally different pages. This isn’t one thing that frameworks like Bootstrap and Tailwind can do, and utilizing media queries to drag it off could be a nightmare. (You possibly can nonetheless use these frameworks utilizing this method, simply with out the necessity for the responsive courses they supply.)
We might use a media question that solely applies to center sized screens, however this doesn’t remedy the difficulty with various props based mostly on display screen width. Fortunately, the method we’re protecting can remedy that. We will modify the earlier code to permit for a {custom} mode per breakpoint by passing it via an array, with the primary merchandise within the array being the smallest display screen measurement.
<CTA :custom-mode=”[‘compact’, ‘default’, ‘compact’]” />
First, let’s replace the props that the <CTA /> element can settle for:
props: {
displayMode: {
sort: String,
default: “default”
},
customMode: {
sort: [Boolean, Array],
default: false
},
}
We will then add the next to generate to appropriate mode:
import { computed } from “vue”;
import useBreakpoints from “@/composables/useBreakpoints”;
// …
setup(props) {
const { currentBreakpoint } = useBreakpoints()
const mode = computed(() => {
if(props.customMode) {
return props.customMode[currentBreakpoint.value] ?? props.displayMode
}
return props.displayMode
})
return { mode }
},
That is taking the mode from the array based mostly on the present breakpoint, and defaults to the displayMode if one isn’t discovered. Then we are able to use mode as an alternative to fashion the element.
Extraction for reusability
Many of those strategies may be extracted into further composables and mixins that may be reuseD with different elements.
Extracting computed mode
The logic for returning the right mode may be extracted right into a composable:
// composables/useResponsive.js
import { computed } from “vue”;
import useBreakpoints from “@/composables/useBreakpoints”;
export const useResponsive = (props) => {
const { currentBreakpoint } = useBreakpoints()
const mode = computed(() => {
if(props.customMode) {
return props.customMode[currentBreakpoint.value] ?? props.displayMode
}
return props.displayMode
})
return { mode }
}
Extracting props
In Vue 2, we might repeat props was by utilizing mixins, however there are noticeable drawbacks. Vue 3 permits us to merge these with different props utilizing the identical composable. There’s a small caveat with this, as IDEs appear unable to acknowledge props for autocompletion utilizing this methodology. If that is too annoying, you should use a mixin as an alternative.
Optionally, we are able to additionally move {custom} validation to verify we’re utilizing the modes solely obtainable to every element, the place the primary worth handed via to the validator is the default.
// composables/useResponsive.js
// …
export const withResponsiveProps = (validation, props) => {
return {
displayMode: {
sort: String,
default: validation[0],
validator: perform (worth) {
return validation.indexOf(worth) !== -1
}
},
customMode: {
sort: [Boolean, Array],
default: false,
validator: perform (worth) {
return worth ? worth.each(mode => validation.consists of(mode)) : true
}
},
…props
}
}
Now let’s transfer the logic out and import these as an alternative:
// elements/CTA.vue
import Btn from “@/elements/ui/Btn”;
import { useResponsive, withResponsiveProps } from “@/composables/useResponsive”;
export default {
identify: “CTA”,
elements: { Btn },
props: withResponsiveProps([‘default ‘compact’], {
extraPropExample: {
sort: String,
},
}),
setup(props) {
const { mode } = useResponsive(props)
return { mode }
}
}
Conclusion
Making a design system of reusable and responsive elements is difficult and liable to inconsistencies. Plus, we noticed how straightforward it’s to wind up with a load of duplicated code. There’s a wonderful steadiness in the case of creating elements that not solely work in lots of contexts, however play effectively with different elements after they’re mixed.
I’m certain you’ve come throughout this form of state of affairs in your individual work. Utilizing these strategies can cut back the issue and hopefully make the UI extra secure, reusable, maintainable, and straightforward to make use of.
Avoiding the Pitfalls of Nested Elements in a Design System initially revealed on CSS-Methods. It’s best to get the e-newsletter.
Subscribe to MarketingSolution.
Receive web development discounts & web design tutorials.
Now! Lets GROW Together!