Created
February 2, 2023 19:33
-
-
Save jeremyfrank/ba8823e60da90dee726ff1f6c126fb40 to your computer and use it in GitHub Desktop.
Fluid Typography Tailwind Plugin
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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