diff --git a/.changeset/kind-avocados-poke.md b/.changeset/kind-avocados-poke.md
new file mode 100644
index 000000000..32930b83b
--- /dev/null
+++ b/.changeset/kind-avocados-poke.md
@@ -0,0 +1,9 @@
+---
+'@primer/react-brand': minor
+---
+
+Anti-aliasing is now applied automatically to all `Text` instances _except_ under these conditions:
+
+- When explicitly disabled via `antialiasing={false}`
+- When font weight is `light` or `extralight` AND size is `'100'` or `'200'`
+- When size is `100` (regardless of weight)
diff --git a/packages/react/src/Accordion/Accordion.module.css b/packages/react/src/Accordion/Accordion.module.css
index 5b8b10447..8b9b2e39f 100644
--- a/packages/react/src/Accordion/Accordion.module.css
+++ b/packages/react/src/Accordion/Accordion.module.css
@@ -144,6 +144,11 @@ details[open] > .Accordion__summary.Accordion__summary--default .Accordion__summ
line-height: var(--brand-text-lineHeight-200);
}
+[data-color-mode='dark'] .Accordion__content {
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: auto;
+}
+
.Accordion__content p:not(:first-child) {
margin-block-start: var(--base-size-16);
}
diff --git a/packages/react/src/InlineLink/InlineLink.module.css b/packages/react/src/InlineLink/InlineLink.module.css
index 5d1fbc0ee..4cad5ef08 100644
--- a/packages/react/src/InlineLink/InlineLink.module.css
+++ b/packages/react/src/InlineLink/InlineLink.module.css
@@ -23,3 +23,8 @@
.InlineLink:active > span {
color: var(--brand-InlineLink-color-pressed);
}
+
+[data-color-mode='dark'] .InlineLink {
+ -webkit-font-smoothing: subpixel-antialiased;
+ -moz-osx-font-smoothing: auto;
+}
diff --git a/packages/react/src/Label/Label.module.css b/packages/react/src/Label/Label.module.css
index 52ff419b0..aef2fef00 100644
--- a/packages/react/src/Label/Label.module.css
+++ b/packages/react/src/Label/Label.module.css
@@ -20,6 +20,11 @@
text-align: center;
}
+[data-color-mode='dark'] .Label {
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: auto;
+}
+
.Label::before {
content: '';
position: absolute;
diff --git a/packages/react/src/Prose/Prose.module.css b/packages/react/src/Prose/Prose.module.css
index 2a087171b..8316dd844 100644
--- a/packages/react/src/Prose/Prose.module.css
+++ b/packages/react/src/Prose/Prose.module.css
@@ -34,6 +34,8 @@
/* ---------------------------------------------------------- */
.Prose * + * {
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: auto;
margin-block-start: var(--spacing, 1em);
}
diff --git a/packages/react/src/SubNav/SubNav.module.css b/packages/react/src/SubNav/SubNav.module.css
index bc48ca5c6..8e8f31385 100644
--- a/packages/react/src/SubNav/SubNav.module.css
+++ b/packages/react/src/SubNav/SubNav.module.css
@@ -16,6 +16,11 @@
z-index: 1;
}
+[data-color-mode='dark'] .SubNav {
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+}
+
.SubNav__heading {
font-weight: var(--base-text-weight-semibold);
color: var(--brand-color-text-default);
diff --git a/packages/react/src/Text/Text.module.css b/packages/react/src/Text/Text.module.css
index 48263842c..eb8a54157 100644
--- a/packages/react/src/Text/Text.module.css
+++ b/packages/react/src/Text/Text.module.css
@@ -2,6 +2,11 @@
font-family: var(--brand-body-fontFamily);
}
+[data-color-mode='dark'] .Text--antialiased {
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: auto;
+}
+
.Text--default {
color: var(--brand-color-text-default);
}
diff --git a/packages/react/src/Text/Text.module.css.d.ts b/packages/react/src/Text/Text.module.css.d.ts
index bf8e64ef6..dce7fd144 100644
--- a/packages/react/src/Text/Text.module.css.d.ts
+++ b/packages/react/src/Text/Text.module.css.d.ts
@@ -1,5 +1,6 @@
declare const styles: {
readonly "Text": string;
+ readonly "Text--antialiased": string;
readonly "Text--default": string;
readonly "Text--muted": string;
readonly "Text-font--mona-sans": string;
diff --git a/packages/react/src/Text/Text.stories.tsx b/packages/react/src/Text/Text.stories.tsx
index 34baf6571..536a8a2a0 100644
--- a/packages/react/src/Text/Text.stories.tsx
+++ b/packages/react/src/Text/Text.stories.tsx
@@ -1,7 +1,7 @@
import {Meta, StoryFn} from '@storybook/react'
import React from 'react'
import {Text, TextFontVariants, TextSizes, TextWeights} from '.'
-import {Stack, Box, Grid} from '../'
+import {Stack, Box, Grid, ThemeProvider} from '../'
import styles from './Text.stories.module.css'
@@ -43,6 +43,23 @@ Playground.args = {
size: '200',
}
+export const AntiAliasingOff = () => (
+
+
+
+ Anti aliasing is disabled for light text at 16px or smaller on a dark backgrounds.
+
+
+ Anti aliasing is enabled for medium text at 16px or larger on a dark backgrounds.
+
+
+
+)
+AntiAliasingOff.args = {
+ size: '200',
+ weight: 'light',
+}
+
export const Scale: StoryFn = args => (
<>
{TextSizes.map(size => (
diff --git a/packages/react/src/Text/Text.test.tsx b/packages/react/src/Text/Text.test.tsx
index 9acac0eeb..44ebecaba 100644
--- a/packages/react/src/Text/Text.test.tsx
+++ b/packages/react/src/Text/Text.test.tsx
@@ -160,4 +160,52 @@ describe('Text', () => {
}
}
})
+ it('should not apply antialiasing when disabled', () => {
+ const mockId = 'mock-id'
+ const {getByText} = render(
+
+ AA disabled
+ ,
+ )
+ const el = getByText('AA disabled')
+ expect(el).not.toHaveClass('Text--antialiased')
+ })
+
+ it('should not apply antialiasing for light weight and small sizes', () => {
+ const {getByText} = render(
+ <>
+
+ light 100
+
+
+ light 200
+
+
+ extralight 100
+
+
+ extralight 200
+
+ >,
+ )
+
+ expect(getByText('light 100')).not.toHaveClass('Text--antialiased')
+ expect(getByText('light 200')).not.toHaveClass('Text--antialiased')
+ expect(getByText('extralight 100')).not.toHaveClass('Text--antialiased')
+ expect(getByText('extralight 200')).not.toHaveClass('Text--antialiased')
+ })
+
+ it('should not apply antialiasing for size 100', () => {
+ const {getByText} = render(size 100)
+ expect(getByText('size 100')).not.toHaveClass('Text--antialiased')
+ })
+
+ it('should apply antialiasing by default for other combinations', () => {
+ const {getByText} = render(
+
+ medium 300
+ ,
+ )
+ expect(getByText('medium 300')).toHaveClass('Text--antialiased')
+ })
})
diff --git a/packages/react/src/Text/Text.tsx b/packages/react/src/Text/Text.tsx
index 90ea880c5..ee4c0aca1 100644
--- a/packages/react/src/Text/Text.tsx
+++ b/packages/react/src/Text/Text.tsx
@@ -67,6 +67,10 @@ export type TextProps = {
* Note: Only applies to block elements.
*/
align?: 'start' | 'center' | 'end'
+ /**
+ * Toggle antialiasing on or off
+ */
+ antialiasing?: boolean
} & TextTags
export function Text({
@@ -80,6 +84,7 @@ export function Text({
variant = defaultTextVariant,
weight,
style,
+ antialiasing = true,
...rest
}: PropsWithChildren) {
const {classes: animationClasses, styles: animationInlineStyles} = useAnimation(animate)
@@ -96,12 +101,22 @@ export function Text({
.join(' ')
}, [weight])
+ const dontApplyAA = Boolean(
+ // When explicitly disabled
+ !antialiasing ||
+ // When selected font weight is not suitable for anti-aliasing
+ (weight && ['light', 'extralight'].includes(weight as TextWeightVariants) && ['100', '200'].includes(size)) ||
+ // When size is too small
+ size === '100',
+ )
+
const textClassName = clsx(
animationClasses,
styles.Text,
styles[`Text-font--${font}`],
styles[`Text--${variant}`],
styles[`Text--${size}`],
+ !dontApplyAA && styles['Text--antialiased'],
weight && weightClass,
align && styles[`Text-align--${align}`],
className,
diff --git a/packages/react/src/Text/Text.visual.spec.ts b/packages/react/src/Text/Text.visual.spec.ts
index df7cfd6a5..b07155160 100644
--- a/packages/react/src/Text/Text.visual.spec.ts
+++ b/packages/react/src/Text/Text.visual.spec.ts
@@ -21,6 +21,13 @@ test.describe('Visual Comparison: Text', () => {
expect(await page.screenshot({fullPage: true})).toMatchSnapshot()
})
+ test('Text / Anti Aliasing Off', async ({page}) => {
+ await page.goto('http://localhost:6006/iframe.html?args=&id=components-text--anti-aliasing-off&viewMode=story')
+
+ await page.waitForTimeout(500)
+ expect(await page.screenshot({fullPage: true})).toMatchSnapshot()
+ })
+
test('Text / Scale', async ({page}) => {
await page.goto('http://localhost:6006/iframe.html?args=&id=components-text--scale&viewMode=story')
diff --git a/packages/react/src/Text/Text.visual.spec.ts-snapshots/Visual-Comparison-Text-Text-Anti-Aliasing-Off-1-linux.png b/packages/react/src/Text/Text.visual.spec.ts-snapshots/Visual-Comparison-Text-Text-Anti-Aliasing-Off-1-linux.png
new file mode 100644
index 000000000..b7d613337
Binary files /dev/null and b/packages/react/src/Text/Text.visual.spec.ts-snapshots/Visual-Comparison-Text-Text-Anti-Aliasing-Off-1-linux.png differ