Skip to content

Instantly share code, notes, and snippets.

@jeremyfrank
Created February 2, 2023 19:33
Show Gist options
  • Save jeremyfrank/ba8823e60da90dee726ff1f6c126fb40 to your computer and use it in GitHub Desktop.
Save jeremyfrank/ba8823e60da90dee726ff1f6c126fb40 to your computer and use it in GitHub Desktop.
Fluid Typography Tailwind Plugin
const plugin = require('tailwindcss/plugin')
const isPlainObject = require('tailwindcss/lib/util/isPlainObject').default
const { rem } = require('@viget/tailwindcss-plugins/utilities/fns')
/**
* Outputs various typography styles as Tailwind components
*
* .heading-NAME-SIZE
* .body-text-SIZE
* .ui-text-SIZE
* .utility-text-SIZE
* .button-text-SIZE
*/
const percentToEm = (percent) => `${percent / 100}em`
/**
* Adapted from https://www.smashingmagazine.com/2022/01/modern-fluid-typography-css-clamp/
*
* @param {Number} minSize Minimum font size in pixels
* @param {Number} maxSize Maximum font size in pixels
* @param {String} unit The unit to use for scaling between min & max. Defaults to `cqi`.
* @param {Number} minContainer Minimum container size in pixels
* @param {Number} maxContainer Maximum container size in pixels
* @returns
*/
const fluidFontSize = (
minSize,
maxSize,
unit = 'cqi',
minContainer = 480,
maxContainer = 1200
) => {
const clamp = (min, val, max) => `clamp(${[min, val, max].join(', ')})`
const slope = (maxSize - minSize) / (maxContainer - minContainer)
const slopeToUnit = Number((slope * 100).toFixed(2))
const interceptRem = (minSize - slope * minContainer).toFixed(2)
const fluidVal = `${slopeToUnit}${unit} + ${rem(interceptRem)}`
const finalSizeValue = clamp(rem(minSize), fluidVal, rem(maxSize))
return finalSizeValue
}
module.exports = plugin(({ matchComponents, theme }) => {
const headingCondensedValues = {
'2xl': [
[rem(80), fluidFontSize(48, 80)],
{
fontFamily: theme('fontFamily.display'),
fontWeight: 600,
lineHeight: 0.9,
letterSpacing: percentToEm(1),
textTransform: 'uppercase',
},
],
xl: [
[rem(64), fluidFontSize(32, 64)],
{
fontFamily: theme('fontFamily.display'),
fontWeight: 600,
lineHeight: 0.9,
letterSpacing: percentToEm(1),
textTransform: 'uppercase',
},
],
lg: [
[rem(56), fluidFontSize(32, 56)],
{
fontFamily: theme('fontFamily.display'),
fontWeight: 600,
lineHeight: 0.9,
letterSpacing: percentToEm(1),
textTransform: 'uppercase',
},
],
md: [
[rem(48), fluidFontSize(32, 48)],
{
fontFamily: theme('fontFamily.display'),
fontWeight: 600,
lineHeight: 1.1,
letterSpacing: percentToEm(1),
textTransform: 'uppercase',
},
],
sm: [
[rem(32), fluidFontSize(24, 32)],
{
fontFamily: theme('fontFamily.display'),
fontWeight: 600,
lineHeight: 1.1,
letterSpacing: percentToEm(1),
textTransform: 'uppercase',
},
],
xs: [
[rem(24), fluidFontSize(18, 24)],
{
fontFamily: theme('fontFamily.display'),
fontWeight: 600,
lineHeight: 1.1,
letterSpacing: percentToEm(1),
textTransform: 'uppercase',
},
],
}
const headingUtilityValues = {
xl: [
[rem(30), fluidFontSize(20, 30)],
{
fontFamily: theme('fontFamily.sans'),
fontWeight: 600,
lineHeight: 1.3,
letterSpacing: percentToEm(2),
},
],
lg: [
[rem(24), fluidFontSize(16, 24)],
{
fontFamily: theme('fontFamily.sans'),
fontWeight: 600,
lineHeight: 1.3,
letterSpacing: percentToEm(2),
},
],
md: [
[rem(20), fluidFontSize(14, 20)],
{
fontFamily: theme('fontFamily.sans'),
fontWeight: 600,
lineHeight: 1.3,
letterSpacing: percentToEm(2),
},
],
sm: [
[rem(16), fluidFontSize(14, 16)],
{
fontFamily: theme('fontFamily.sans'),
fontWeight: 600,
lineHeight: 1.3,
letterSpacing: percentToEm(2),
},
],
xs: [
rem(14),
{
fontFamily: theme('fontFamily.sans'),
fontWeight: 600,
lineHeight: 1.3,
letterSpacing: percentToEm(2),
},
],
}
const bodyTextValues = {
'2xl': [
[rem(24), fluidFontSize(18, 24)],
{
fontFamily: theme('fontFamily.serif'),
lineHeight: 1.6,
letterSpacing: percentToEm(1),
},
],
xl: [
[rem(20), fluidFontSize(16, 20)],
{
fontFamily: theme('fontFamily.serif'),
lineHeight: 1.6,
letterSpacing: percentToEm(1),
},
],
lg: [
[rem(18), fluidFontSize(14, 18)],
{
fontFamily: theme('fontFamily.serif'),
lineHeight: 1.6,
letterSpacing: percentToEm(1),
},
],
md: [
[rem(16), fluidFontSize(14, 16)],
{
fontFamily: theme('fontFamily.serif'),
lineHeight: 1.6,
letterSpacing: percentToEm(1),
},
],
sm: [
rem(14),
{
fontFamily: theme('fontFamily.serif'),
lineHeight: 1.6,
letterSpacing: percentToEm(1),
},
],
xs: [
rem(12),
{
fontFamily: theme('fontFamily.serif'),
lineHeight: 1.6,
letterSpacing: percentToEm(1),
},
],
}
const uiTextValues = {
lg: [
rem(16),
{
fontFamily: theme('fontFamily.sans'),
lineHeight: 1.5,
letterSpacing: percentToEm(2),
},
],
md: [
rem(14),
{
fontFamily: theme('fontFamily.sans'),
lineHeight: 1.5,
letterSpacing: percentToEm(2),
},
],
sm: [
rem(12),
{
fontFamily: theme('fontFamily.sans'),
lineHeight: 1.5,
letterSpacing: percentToEm(2),
},
],
}
const utilityTextValues = {
lg: [
rem(18),
{
fontFamily: theme('fontFamily.display'),
fontWeight: 600,
lineHeight: 1.1,
letterSpacing: percentToEm(6),
textTransform: 'uppercase',
},
],
md: [
rem(14),
{
fontFamily: theme('fontFamily.display'),
fontWeight: 600,
lineHeight: 1.1,
letterSpacing: percentToEm(6),
textTransform: 'uppercase',
},
],
sm: [
rem(12),
{
fontFamily: theme('fontFamily.display'),
fontWeight: 600,
lineHeight: 1.1,
letterSpacing: percentToEm(6),
textTransform: 'uppercase',
},
],
}
const buttonTextValues = {
lg: [
rem(18),
{
fontFamily: theme('fontFamily.sans'),
fontWeight: 600,
lineHeight: 1.1,
letterSpacing: percentToEm(2),
},
],
md: [
rem(14),
{
fontFamily: theme('fontFamily.sans'),
fontWeight: 600,
lineHeight: 1.1,
letterSpacing: percentToEm(2),
},
],
sm: [
rem(12),
{
fontFamily: theme('fontFamily.sans'),
fontWeight: 600,
lineHeight: 1.1,
letterSpacing: percentToEm(2),
},
],
}
const outputTypeStyles = (name, values) => {
matchComponents(
{
[`${name}`]: (value) => {
let [fontSize, options] = Array.isArray(value) ? value : [value]
let {
lineHeight,
letterSpacing,
fontFamily,
fontWeight,
textTransform,
} = isPlainObject(options) ? options : { lineHeight: options }
return {
'font-size': fontSize,
...(lineHeight === undefined ? {} : { 'line-height': lineHeight }),
...(letterSpacing === undefined
? {}
: { 'letter-spacing': letterSpacing }),
...(fontFamily === undefined ? {} : { 'font-family': fontFamily }),
...(fontWeight === undefined ? {} : { 'font-weight': fontWeight }),
...(textTransform === undefined
? {}
: { 'text-transform': textTransform }),
}
},
},
{
values: values,
type: ['absolute-size', 'relative-size', 'length', 'percentage'],
}
)
}
outputTypeStyles('heading-condensed', headingCondensedValues)
outputTypeStyles('heading-utility', headingUtilityValues)
outputTypeStyles('body-text', bodyTextValues)
outputTypeStyles('ui-text', uiTextValues)
outputTypeStyles('utility-text', utilityTextValues)
outputTypeStyles('button-text', buttonTextValues)
})
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment