Skip to content

Commit 5e255de

Browse files
philipp-spiessthecrypticaceRobinMalfait
authored
Add text-shadow-* utilities (#17389)
This PR adds new `text-shadow-*` utilities and default values courtesy of @danhollick's. Usage is similar to the normal `shadow-*` utilities, for example: ```html <h1 class="text-center text-7xl tracking-tight text-white text-shadow-xl"> Some fancy <br /> headline </h1> ``` Since this PR also adds first-class support for the `--text-shadow` theme namespace, it also means it resolves #17047. ## Test plan - Copied @danhollick's [demo playground](https://play.tailwindcss.com/lbA3Y6VY8u) to the `vite` setup and ran a production build to ensure it looks correct: https://tailwind-text-shadows-preview-2kdnjb32b.vercel.app/ --------- Co-authored-by: Jordan Pittman <jordan@cryptica.me> Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
1 parent 2af7c57 commit 5e255de

File tree

8 files changed

+375
-4
lines changed

8 files changed

+375
-4
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
2020
- _Experimental_: Add `wrap-anywhere`, `wrap-break-word`, and `wrap-normal` utilities ([#12128](https://github.com/tailwindlabs/tailwindcss/pull/12128))
2121
- _Experimental_: Add `@source inline(…)` ([#17147](https://github.com/tailwindlabs/tailwindcss/pull/17147))
2222
- _Experimental_: Add `@source not` ([#17255](https://github.com/tailwindlabs/tailwindcss/pull/17255))
23+
- _Experimental_: Add `text-shadow-*` utilities ([#17389](https://github.com/tailwindlabs/tailwindcss/pull/17389))
2324
- Added new `bg-{top,bottom}-{left,right}` utilities ([#17378](https://github.com/tailwindlabs/tailwindcss/pull/17378))
2425
- Added new `bg-{position,size}-*` utilities for arbitrary values ([#17432](https://github.com/tailwindlabs/tailwindcss/pull/17432))
2526

packages/tailwindcss/src/__snapshots__/intellisense.test.ts.snap

+69
Original file line numberDiff line numberDiff line change
@@ -7756,6 +7756,75 @@ exports[`getClassList 1`] = `
77567756
"text-nowrap",
77577757
"text-pretty",
77587758
"text-right",
7759+
"text-shadow",
7760+
"text-shadow-current",
7761+
"text-shadow-current/0",
7762+
"text-shadow-current/5",
7763+
"text-shadow-current/10",
7764+
"text-shadow-current/15",
7765+
"text-shadow-current/20",
7766+
"text-shadow-current/25",
7767+
"text-shadow-current/30",
7768+
"text-shadow-current/35",
7769+
"text-shadow-current/40",
7770+
"text-shadow-current/45",
7771+
"text-shadow-current/50",
7772+
"text-shadow-current/55",
7773+
"text-shadow-current/60",
7774+
"text-shadow-current/65",
7775+
"text-shadow-current/70",
7776+
"text-shadow-current/75",
7777+
"text-shadow-current/80",
7778+
"text-shadow-current/85",
7779+
"text-shadow-current/90",
7780+
"text-shadow-current/95",
7781+
"text-shadow-current/100",
7782+
"text-shadow-inherit",
7783+
"text-shadow-inherit/0",
7784+
"text-shadow-inherit/5",
7785+
"text-shadow-inherit/10",
7786+
"text-shadow-inherit/15",
7787+
"text-shadow-inherit/20",
7788+
"text-shadow-inherit/25",
7789+
"text-shadow-inherit/30",
7790+
"text-shadow-inherit/35",
7791+
"text-shadow-inherit/40",
7792+
"text-shadow-inherit/45",
7793+
"text-shadow-inherit/50",
7794+
"text-shadow-inherit/55",
7795+
"text-shadow-inherit/60",
7796+
"text-shadow-inherit/65",
7797+
"text-shadow-inherit/70",
7798+
"text-shadow-inherit/75",
7799+
"text-shadow-inherit/80",
7800+
"text-shadow-inherit/85",
7801+
"text-shadow-inherit/90",
7802+
"text-shadow-inherit/95",
7803+
"text-shadow-inherit/100",
7804+
"text-shadow-initial",
7805+
"text-shadow-none",
7806+
"text-shadow-transparent",
7807+
"text-shadow-transparent/0",
7808+
"text-shadow-transparent/5",
7809+
"text-shadow-transparent/10",
7810+
"text-shadow-transparent/15",
7811+
"text-shadow-transparent/20",
7812+
"text-shadow-transparent/25",
7813+
"text-shadow-transparent/30",
7814+
"text-shadow-transparent/35",
7815+
"text-shadow-transparent/40",
7816+
"text-shadow-transparent/45",
7817+
"text-shadow-transparent/50",
7818+
"text-shadow-transparent/55",
7819+
"text-shadow-transparent/60",
7820+
"text-shadow-transparent/65",
7821+
"text-shadow-transparent/70",
7822+
"text-shadow-transparent/75",
7823+
"text-shadow-transparent/80",
7824+
"text-shadow-transparent/85",
7825+
"text-shadow-transparent/90",
7826+
"text-shadow-transparent/95",
7827+
"text-shadow-transparent/100",
77597828
"text-start",
77607829
"text-transparent",
77617830
"text-transparent/0",

packages/tailwindcss/src/feature-flags.ts

+1
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,6 @@ export const enableSafeAlignment = process.env.FEATURES_ENV !== 'stable'
66
export const enableScripting = process.env.FEATURES_ENV !== 'stable'
77
export const enableSourceInline = process.env.FEATURES_ENV !== 'stable'
88
export const enableSourceNot = process.env.FEATURES_ENV !== 'stable'
9+
export const enableTextShadows = process.env.FEATURES_ENV !== 'stable'
910
export const enableUserValid = process.env.FEATURES_ENV !== 'stable'
1011
export const enableWrapAnywhere = process.env.FEATURES_ENV !== 'stable'

packages/tailwindcss/src/theme.ts

+4-3
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,11 @@ const ignoredThemeKeyMap = new Map([
2121
'--text',
2222
[
2323
'--text-color',
24-
'--text-underline-offset',
25-
'--text-indent',
26-
'--text-decoration-thickness',
2724
'--text-decoration-color',
25+
'--text-decoration-thickness',
26+
'--text-indent',
27+
'--text-shadow',
28+
'--text-underline-offset',
2829
],
2930
],
3031
])

packages/tailwindcss/src/utilities.test.ts

+154
Original file line numberDiff line numberDiff line change
@@ -15432,6 +15432,160 @@ test('text', async () => {
1543215432
).toEqual('')
1543315433
})
1543415434

15435+
test('text-shadow', async () => {
15436+
expect(
15437+
await compileCss(
15438+
css`
15439+
@theme {
15440+
--color-red-500: #ef4444;
15441+
--text-shadow-2xs: 0px 1px 0px rgb(0 0 0 / 0.1);
15442+
--text-shadow-sm: 0px 1px 2px rgb(0 0 0 / 0.06), 0px 2px 2px rgb(0 0 0 / 0.06);
15443+
}
15444+
@tailwind utilities;
15445+
`,
15446+
[
15447+
// Shadows
15448+
'text-shadow-2xs',
15449+
'text-shadow-sm',
15450+
'text-shadow-none',
15451+
'text-shadow-[12px_12px_#0088cc]',
15452+
'text-shadow-[10px_10px]',
15453+
'text-shadow-[var(--value)]',
15454+
'text-shadow-[shadow:var(--value)]',
15455+
15456+
// Colors
15457+
'text-shadow-red-500',
15458+
'text-shadow-red-500/50',
15459+
'text-shadow-red-500/2.25',
15460+
'text-shadow-red-500/2.5',
15461+
'text-shadow-red-500/2.75',
15462+
'text-shadow-red-500/[0.5]',
15463+
'text-shadow-red-500/[50%]',
15464+
'text-shadow-current',
15465+
'text-shadow-current/50',
15466+
'text-shadow-current/[0.5]',
15467+
'text-shadow-current/[50%]',
15468+
'text-shadow-inherit',
15469+
'text-shadow-transparent',
15470+
'text-shadow-[#0088cc]',
15471+
'text-shadow-[#0088cc]/50',
15472+
'text-shadow-[#0088cc]/[0.5]',
15473+
'text-shadow-[#0088cc]/[50%]',
15474+
'text-shadow-[color:var(--value)]',
15475+
'text-shadow-[color:var(--value)]/50',
15476+
'text-shadow-[color:var(--value)]/[0.5]',
15477+
'text-shadow-[color:var(--value)]/[50%]',
15478+
],
15479+
),
15480+
).toMatchInlineSnapshot(`
15481+
":root, :host {
15482+
--color-red-500: #ef4444;
15483+
}
15484+
15485+
.text-shadow-2xs {
15486+
text-shadow: 0px 1px 0px var(--tw-text-shadow-color, #0000001a);
15487+
}
15488+
15489+
.text-shadow-\\[\\#0088cc\\] {
15490+
--tw-text-shadow-color: #08c;
15491+
}
15492+
15493+
.text-shadow-\\[\\#0088cc\\]\\/50, .text-shadow-\\[\\#0088cc\\]\\/\\[0\\.5\\], .text-shadow-\\[\\#0088cc\\]\\/\\[50\\%\\] {
15494+
--tw-text-shadow-color: oklab(59.9824% -.06725 -.12414 / .5);
15495+
}
15496+
15497+
.text-shadow-\\[10px_10px\\] {
15498+
text-shadow: 10px 10px var(--tw-text-shadow-color, currentcolor);
15499+
}
15500+
15501+
.text-shadow-\\[12px_12px_\\#0088cc\\] {
15502+
text-shadow: 12px 12px var(--tw-text-shadow-color, #08c);
15503+
}
15504+
15505+
.text-shadow-\\[color\\:var\\(--value\\)\\] {
15506+
--tw-text-shadow-color: var(--value);
15507+
}
15508+
15509+
.text-shadow-\\[color\\:var\\(--value\\)\\]\\/50, .text-shadow-\\[color\\:var\\(--value\\)\\]\\/\\[0\\.5\\], .text-shadow-\\[color\\:var\\(--value\\)\\]\\/\\[50\\%\\] {
15510+
--tw-text-shadow-color: color-mix(in oklab, var(--value) 50%, transparent);
15511+
}
15512+
15513+
.text-shadow-\\[shadow\\:var\\(--value\\)\\], .text-shadow-\\[var\\(--value\\)\\] {
15514+
text-shadow: var(--value);
15515+
}
15516+
15517+
.text-shadow-current {
15518+
--tw-text-shadow-color: currentColor;
15519+
}
15520+
15521+
.text-shadow-current\\/50, .text-shadow-current\\/\\[0\\.5\\], .text-shadow-current\\/\\[50\\%\\] {
15522+
--tw-text-shadow-color: color-mix(in oklab, currentColor 50%, transparent);
15523+
}
15524+
15525+
.text-shadow-inherit {
15526+
--tw-text-shadow-color: inherit;
15527+
}
15528+
15529+
.text-shadow-none {
15530+
text-shadow: none;
15531+
}
15532+
15533+
.text-shadow-red-500 {
15534+
--tw-text-shadow-color: var(--color-red-500);
15535+
}
15536+
15537+
.text-shadow-red-500\\/2\\.5 {
15538+
--tw-text-shadow-color: color-mix(in oklab, var(--color-red-500) 2.5%, transparent);
15539+
}
15540+
15541+
.text-shadow-red-500\\/2\\.25 {
15542+
--tw-text-shadow-color: color-mix(in oklab, var(--color-red-500) 2.25%, transparent);
15543+
}
15544+
15545+
.text-shadow-red-500\\/2\\.75 {
15546+
--tw-text-shadow-color: color-mix(in oklab, var(--color-red-500) 2.75%, transparent);
15547+
}
15548+
15549+
.text-shadow-red-500\\/50, .text-shadow-red-500\\/\\[0\\.5\\], .text-shadow-red-500\\/\\[50\\%\\] {
15550+
--tw-text-shadow-color: color-mix(in oklab, var(--color-red-500) 50%, transparent);
15551+
}
15552+
15553+
.text-shadow-sm {
15554+
text-shadow: 0px 1px 2px var(--tw-text-shadow-color, #0000000f), 0px 2px 2px var(--tw-text-shadow-color, #0000000f);
15555+
}
15556+
15557+
.text-shadow-transparent {
15558+
--tw-text-shadow-color: transparent;
15559+
}
15560+
15561+
@property --tw-text-shadow-color {
15562+
syntax: "*";
15563+
inherits: false
15564+
}"
15565+
`)
15566+
expect(
15567+
await run([
15568+
'-shadow-xl',
15569+
'-shadow-none',
15570+
'-shadow-red-500',
15571+
'-shadow-red-500/50',
15572+
'-shadow-red-500/[0.5]',
15573+
'-shadow-red-500/[50%]',
15574+
'-shadow-current',
15575+
'-shadow-current/50',
15576+
'-shadow-current/[0.5]',
15577+
'-shadow-current/[50%]',
15578+
'-shadow-inherit',
15579+
'-shadow-transparent',
15580+
'-shadow-[#0088cc]',
15581+
'-shadow-[#0088cc]/50',
15582+
'-shadow-[#0088cc]/[0.5]',
15583+
'-shadow-[#0088cc]/[50%]',
15584+
'-shadow-[var(--value)]',
15585+
]),
15586+
).toEqual('')
15587+
})
15588+
1543515589
test('shadow', async () => {
1543615590
expect(
1543715591
await compileCss(

packages/tailwindcss/src/utilities.ts

+97-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,12 @@ import {
1111
} from './ast'
1212
import type { Candidate, CandidateModifier, NamedUtilityValue } from './candidate'
1313
import type { DesignSystem } from './design-system'
14-
import { enableBaselineLast, enableSafeAlignment, enableWrapAnywhere } from './feature-flags'
14+
import {
15+
enableBaselineLast,
16+
enableSafeAlignment,
17+
enableTextShadows,
18+
enableWrapAnywhere,
19+
} from './feature-flags'
1520
import type { Theme, ThemeKey } from './theme'
1621
import { compareBreakpoints } from './utils/compare-breakpoints'
1722
import { DefaultMap } from './utils/default-map'
@@ -4254,6 +4259,97 @@ export function createUtilities(theme: Theme) {
42544259
},
42554260
])
42564261

4262+
if (enableTextShadows) {
4263+
let textShadowProperties = () => {
4264+
return atRoot([property('--tw-text-shadow-color')])
4265+
}
4266+
4267+
staticUtility('text-shadow-initial', [
4268+
textShadowProperties,
4269+
['--tw-text-shadow-color', 'initial'],
4270+
])
4271+
4272+
utilities.functional('text-shadow', (candidate) => {
4273+
if (!candidate.value) {
4274+
let value = theme.get(['--text-shadow'])
4275+
if (value === null) return
4276+
4277+
return [
4278+
textShadowProperties(),
4279+
decl(
4280+
'text-shadow',
4281+
replaceShadowColors(value, (color) => `var(--tw-text-shadow-color, ${color})`),
4282+
),
4283+
]
4284+
}
4285+
4286+
if (candidate.value.kind === 'arbitrary') {
4287+
let value: string | null = candidate.value.value
4288+
let type = candidate.value.dataType ?? inferDataType(value, ['color'])
4289+
4290+
switch (type) {
4291+
case 'color': {
4292+
value = asColor(value, candidate.modifier, theme)
4293+
if (value === null) return
4294+
4295+
return [textShadowProperties(), decl('--tw-text-shadow-color', value)]
4296+
}
4297+
default: {
4298+
return [
4299+
textShadowProperties(),
4300+
decl(
4301+
'text-shadow',
4302+
replaceShadowColors(value, (color) => `var(--tw-text-shadow-color, ${color})`),
4303+
),
4304+
]
4305+
}
4306+
}
4307+
}
4308+
4309+
switch (candidate.value.value) {
4310+
case 'none':
4311+
if (candidate.modifier) return
4312+
return [textShadowProperties(), decl('text-shadow', 'none')]
4313+
}
4314+
4315+
// Shadow size
4316+
{
4317+
let value = theme.get([`--text-shadow-${candidate.value.value}`])
4318+
if (value) {
4319+
if (candidate.modifier) return
4320+
return [
4321+
textShadowProperties(),
4322+
decl(
4323+
'text-shadow',
4324+
replaceShadowColors(value, (color) => `var(--tw-text-shadow-color, ${color})`),
4325+
),
4326+
]
4327+
}
4328+
}
4329+
4330+
// Shadow color
4331+
{
4332+
let value = resolveThemeColor(candidate, theme, ['--text-shadow-color', '--color'])
4333+
if (value) {
4334+
return [textShadowProperties(), decl('--tw-text-shadow-color', value)]
4335+
}
4336+
}
4337+
})
4338+
4339+
suggest('text-shadow', () => [
4340+
{
4341+
values: ['current', 'inherit', 'transparent'],
4342+
valueThemeKeys: ['--text-shadow-color', '--color'],
4343+
modifiers: Array.from({ length: 21 }, (_, index) => `${index * 5}`),
4344+
},
4345+
{
4346+
values: ['none'],
4347+
valueThemeKeys: ['--text-shadow'],
4348+
hasDefaultValue: true,
4349+
},
4350+
])
4351+
}
4352+
42574353
{
42584354
let cssBoxShadowValue = [
42594355
'var(--tw-inset-shadow)',

0 commit comments

Comments
 (0)