Whenever I start a new project, the first order of business is to sand down some of the rough edges in the CSS language. I do this with a functional set of custom baseline styles.
每当我开始一个新项目时,第一件事就是打磨一下 CSS 语言中的一些粗糙边缘。我通过一套功能性的自定义基础样式来做到这一点。
For a long time, I used Eric Meyer's famous CSS Reset(opens in new tab). It's a solid chunk of CSS, but it's a bit long in the tooth at this point; it hasn't been updated in more than a decade, and a lot has changed since then!
很长一段时间,我一直在使用埃里克·梅耶的著名 CSSReset。这是一段很扎实的 CSS,但现在有点过时了;它已经十多年没有更新了,这期间发生了很多变化!
Recently, I've been using my own custom CSS reset. It includes all of the little tricks I've discovered to improve both the user experience and the CSS authoring experience.
最近,我一直在使用我自己的自定义 CSS 重置。它包含了我发现的所有小技巧,以改善用户体验和 CSS 编写体验。
Like other CSS resets, it's unopinionated when it comes to design / cosmetics. You can use this reset for any project, no matter the aesthetic you're going for.
像其他 CSS 重置一样,它在设计/外观方面没有偏见。您可以将此重置用于任何项目,无论您追求何种美学。
In this tutorial, we'll go on a tour of my custom CSS reset. We'll dig into each rule, and you'll learn what it does and why you might want to use it!
在本教程中,我们将对我的自定义 CSS 重置进行一次巡览。我们将深入每条规则,您将了解它的作用以及您为什么可能想要使用它!
Link to this headingThe CSS Reset CSS 重置
Without further ado, here it is:
不再赘述,给你看:
/* 1. Use a more-intuitive box-sizing model */
*, *::before, *::after {
box-sizing: border-box;
}
/* 2. Remove default margin */
* {
margin: 0;
}
body {
/* 3. Add accessible line-height */
line-height: 1.5;
/* 4. Improve text rendering */
-webkit-font-smoothing: antialiased;
}
/* 5. Improve media defaults */
img, picture, video, canvas, svg {
display: block;
max-width: 100%;
}
/* 6. Inherit fonts for form controls */
input, button, textarea, select {
font: inherit;
}
/* 7. Avoid text overflows */
p, h1, h2, h3, h4, h5, h6 {
overflow-wrap: break-word;
}
/* 8. Improve line wrapping */
p {
text-wrap: pretty;
}
h1, h2, h3, h4, h5, h6 {
text-wrap: balance;
}
/*
9. Create a root stacking context
*/
#root, #__next {
isolation: isolate;
}
It's relatively short, but there's a lot of stuff packed into this small stylesheet. Let's get into it!
它相对较短,但这个小样式表中包含了很多内容。让我们开始吧!
Link to this heading1. Box-sizing model 1. 盒子模型
Pop quiz! Measuring by the visible pink border, how wide is the .box
element in the following scenario, assuming no other CSS has been applied?
突击测验!根据可见的粉色边框,在以下情况下, .box
元素的宽度是多少,假设没有应用其他 CSS?
<style>
.parent {
width: 200px;
}
.box {
width: 100%;
border: 2px solid hotpink;
padding: 20px;
}
</style>
<div class="parent">
<div class="box"></div>
</div>
Our .box
element has width: 100%
. Because its parent is 200px wide, that 100% will resolve to 200px.
我们的 .box
元素有 width: 100%
。因为它的父元素宽度为 200px,所以 100% 将解析为 200px。
But where does it apply that 200px width? By default, it will apply that size to the content box.
但是这个 200px 的宽度适用于哪里?默认情况下,它会将该大小应用于内容框。
If you're not familiar, the “content box” is the rectangle in the box model that actually holds the content, inside the border and the padding:
如果你不熟悉,“内容框”是盒模型中实际包含内容的矩形,位于边框和内边距之间:

The width: 100%
declaration will set the .box
's content-box to 200px. The padding will add an extra 40px (20px on each side). The border adds a final 4px (2px on each side). When we do the math, the visible pink rectangle will be 244px wide.
width: 100%
声明将 .box
的内容框设置为 200px。内边距将额外增加 40px(每侧 20px)。边框最终增加 4px(每侧 2px)。当我们进行计算时,可见的粉色矩形将宽 244px。
When we try and cram a 244px box into a 200px-wide parent, it overflows:
当我们尝试将一个 244px 的盒子塞入一个 200px 宽的父元素时,它会溢出:
Code Playground 代码游乐场
Result 结果
This behavior is weird, right? Fortunately, we can change it, by setting the following rule:
这个行为很奇怪,对吧?幸运的是,我们可以通过设置以下规则来改变它:
*, *::before, *::after {
box-sizing: border-box;
}
With this rule applied, percentages will resolve based on the border-box. In the example above, our pink box would be 200px, and the inner content-box would shrink down to 156px (200px - 40px - 4px).
应用此规则后,百分比将基于边框盒进行解析。在上面的示例中,我们的粉色盒子将是 200px,内部内容盒将缩小到 156px(200px - 40px - 4px)。
This is a must-have rule, in my opinion. It makes CSS significantly nicer to work with.
在我看来,这是一个必备的规则。它使得 CSS 的工作变得更加愉快。
We apply it to all elements and pseudo-elements using the wildcard selector (*
). Contrary to popular belief, this is not bad for performance(opens in new tab).
我们将其应用于所有元素和伪元素,使用通配符选择器 ( *
)。与普遍认为的相反,这对性能并没有坏处。
Link to this heading2. Remove default margin 2. 移除默认边距
* {
margin: 0;
}
Browsers make common-sense assumptions around margin. For example, an h1
will include more margin by default than a paragraph.
浏览器对边距做出常识性的假设。例如, h1
默认会比段落包含更多的边距。
These assumptions are reasonable within the context of a word-processing document, but they might not be accurate for a modern web application.
这些假设在文字处理文档的上下文中是合理的,但对于现代 web 应用程序来说,它们可能并不准确。
Margin is a tricky devil, and more often than not, I find myself wishing elements didn't have any by default. So I've decided to remove it all. 🔥
边距是个棘手的家伙,往往我希望元素默认情况下没有任何边距。因此我决定全部去掉。🔥
If/when I do want to add some margin to specific tags, I can do so in my custom project styles. The wildcard selector (*
) has extremely low specificity, so it'll be easy to override this rule.
如果/当我想要为特定标签添加一些边距时,我可以在我的自定义项目样式中做到这一点。通配符选择器 ( *
) 的特异性非常低,因此很容易覆盖此规则。
Link to this heading3. Add accessible line-height
3. 添加可访问的行高
body {
line-height: 1.5;
}
line-height
controls the vertical spacing between each line of text in a paragraph. The default value varies between browsers, but it tends to be around 1.2.
line-height
控制段落中每行文本之间的垂直间距。默认值在不同浏览器之间有所不同,但通常约为 1.2。
This unitless number is a ratio based on the font size. It functions just like the em
unit. With a line-height
of 1.2, each line will be 20% larger than the element's font size.
这个无单位的数字是基于字体大小的比例。它的功能与 em
单位完全相同。使用 line-height
为 1.2 时,每行将比元素的字体大小大 20%。
Here's the problem: for those who are dyslexic, these lines are packed too closely together, making it harder to read. The WCAG criteria states that line-height should be at least 1.5(opens in new tab).
问题是:对于那些有阅读障碍的人来说,这些行之间的间距太近,导致阅读困难。WCAG 标准规定行高应至少为 1.5。
Now, this number does tend to produce quite-large lines on headings and other elements with large type:
现在,这个数字确实倾向于在标题和其他大字号元素上产生相当大的行:
Code Playground 代码游乐场
Result 结果
You may wish to override this value on headings. My understanding is that the WCAG criteria is meant for "body" text, not headings.
您可能希望在标题上覆盖此值。我的理解是,WCAG 标准是针对“正文”文本,而不是标题。
Link to this heading4. Improve text rendering
4. 改善文本渲染
body {
-webkit-font-smoothing: antialiased;
}
Alright, so this one is a bit controversial.
好的,这个有点争议。
On macOS computers, the browser will use “subpixel antialiasing” by default. This is a technique that aims to make text easier to read, by leveraging the R/G/B lights within each pixel.
在 macOS 电脑上,浏览器默认使用“子像素抗锯齿”。这是一种旨在通过利用每个像素内的 R/G/B 光源来使文本更易于阅读的技术。
In the past, this was seen as an accessibility win, because it improved text contrast. You may have read a popular blog post, Stop “Fixing” Font Smoothing(opens in new tab), that advocates against switching to “antialiased”.
在过去,这被视为无障碍的胜利,因为它改善了文本对比度。您可能读过一篇流行的博客文章《停止“修复”字体平滑》,该文章提倡不要切换到“抗锯齿”。
Here's the problem: that article was written in 2012, before the era of high-DPI “retina” displays. Today's pixels are much smaller, invisible to the naked eye.
问题是:那篇文章是 2012 年写的,早于高 DPI“视网膜”显示器的时代。今天的像素要小得多,肉眼无法看见。
The physical arrangement of pixel LEDs has changed as well. If you look at a modern monitor under a microscope, you won't see an orderly grid of R/G/B lines anymore.
像素 LED 的物理排列也发生了变化。如果你在显微镜下观察现代显示器,你将不再看到有序的 R/G/B 线网格。
In macOS Mojave, released in 2018, Apple disabled subpixel antialiasing across the operating system. I'm guessing they realized that it was doing more harm than good on modern hardware.
在 2018 年发布的 macOS Mojave 中,苹果在整个操作系统中禁用了子像素抗锯齿。我猜他们意识到这在现代硬件上造成的伤害大于好处。
Confusingly, macOS browsers like Chrome and Safari still use subpixel antialiasing by default. We need to explicitly turn it off, by setting -webkit-font-smoothing
to antialiased
.
令人困惑的是,macOS 浏览器如 Chrome 和 Safari 默认仍然使用子像素抗锯齿。我们需要通过将 -webkit-font-smoothing
设置为 antialiased
来显式关闭它。
Here's the difference: 这是区别:
Antialiasing 抗锯齿

Subpixel Antialiasing 子像素抗锯齿

macOS is the only operating system to use subpixel-antialiasing, and so this rule has no effect on Windows, Linux, or mobile devices. If you're on a macOS computer, you can experiment with a live render:
macOS 是唯一使用子像素抗锯齿的操作系统,因此此规则对 Windows、Linux 或移动设备没有影响。如果您在 macOS 计算机上,可以尝试实时渲染:
Code Playground 代码游乐场
Result 结果
Link to this heading5. Improve media defaults
5. 改善媒体默认设置
img, picture, video, canvas, svg {
display: block;
max-width: 100%;
}
So here's a weird thing: images are considered "inline" elements. This implies that they should be used in the middle of paragraphs, like <em>
or <strong>
.
所以这里有个奇怪的事情:图像被视为“内联”元素。这意味着它们应该在段落中间使用,比如 <em>
或 <strong>
。
This doesn't jive with how I use images most of the time. Typically, I treat images the same way I treat paragraphs or headers or sidebars; they're layout elements.
这与我大多数时候使用图像的方式不符。通常,我将图像视为与段落、标题或侧边栏相同;它们是布局元素。
If we try to use an inline element in our layout, though, weird things happen. If you've ever had a mysterious 4px gap that wasn't margin or padding or border, it was probably the “inline magic space” that browsers add with line-height
.
如果我们尝试在布局中使用内联元素,会发生奇怪的事情。如果你曾经遇到过一个神秘的 4px 间隙,它既不是边距也不是填充或边框,那很可能是浏览器添加的“内联魔法空格” line-height
。
By setting display: block
on all images by default, we sidestep a whole category of funky issues.
通过默认在所有图像上设置 display: block
,我们避免了一整类奇怪的问题。
I also set max-width: 100%
. This is done to keep large images from overflowing, if they're placed in a container that isn't wide enough to contain them.
我还设置了 max-width: 100%
。这样做是为了防止大图像溢出,如果它们放置在一个不够宽的容器中。
Most block-level elements will automatically grow/shrink to fit their parent, but media elements like <img>
are special: they're known as replaced elements, and they don't follow the same rules.
大多数块级元素会自动增长/缩小以适应其父元素,但像 <img>
这样的媒体元素是特殊的:它们被称为替换元素,并不遵循相同的规则。
If an image has a "native" size of 800×600, the <img>
element will also be 800px wide, even if we plop it into a 500px-wide parent.
如果一张图片的“原生”大小为 800×600,则 <img>
元素的宽度也将为 800px,即使我们将其放入一个 500px 宽的父元素中。
This rule will prevent that image from growing beyond its container, which feels like much more sensible default behavior to me.
此规则将防止该图像超出其容器的大小,这对我来说感觉更像是更合理的默认行为。
Link to this heading6. Inherit fonts for form controls
6. 为表单控件继承字体
input, button, textarea, select {
font: inherit;
}
Here's another weird thing: by default, buttons and inputs don't inherit typographical styles from their parents. Instead, they have their own weird styles.
还有一件奇怪的事情:默认情况下,按钮和输入框不会继承其父元素的排版样式。相反,它们有自己奇怪的样式。
For example, <textarea>
will use the system-default monospace font. Text inputs will use the system-default sans-serif font. And both will choose a microscopically-small font size (13.333px in Chrome).
例如, <textarea>
将使用系统默认的等宽字体。文本输入将使用系统默认的无衬线字体。两者都将选择微小的字体大小(在 Chrome 中为 13.333px)。
As you might imagine, it's very hard to read 13px text on a mobile device. When we focus an input with a small font size, the browser will automatically zoom in, so that the text is easier to read.
正如您所想,在移动设备上阅读 13px 的文本是非常困难的。当我们聚焦一个小字体的输入框时,浏览器会自动放大,以便文本更易于阅读。
Unfortunately, this is not a good experience:
不幸的是,这不是一个好的体验:
If we want to avoid this auto-zoom behavior, the inputs need to have a font-size of at least 1rem / 16px. Here's one way to address the issue:
如果我们想避免这种自动缩放行为,输入框的字体大小需要至少为 1rem / 16px。以下是解决此问题的一种方法:
input, button, textarea, select {
font-size: 1rem;
}
This fixes the auto-zoom issue, but it's a band-aid. Let's address the root cause instead: form inputs shouldn't have their own typographical styles!
这修复了自动缩放问题,但这只是权宜之计。我们应该解决根本原因:表单输入不应该有自己的排版样式!
input, button, textarea, select {
font: inherit;
}
font
is a rarely-used shorthand that sets a bunch of font-related properties, like font-size
, font-weight
, and font-family
. By setting it to inherit
, we instruct these elements to match the typography in their surrounding environment.
font
是一个很少使用的简写,它设置了一堆与字体相关的属性,比如 font-size
、 font-weight
和 font-family
。通过将其设置为 inherit
,我们指示这些元素与其周围环境中的排版相匹配。
As long as we don't choose an obnoxiously small font size for our body text, this solves all of our issues at once. 🎉
只要我们不为正文选择一个令人厌烦的小字体大小,这就能一次性解决我们所有的问题。🎉
Link to this heading7. Avoid text overflows 7. 避免文本溢出
p, h1, h2, h3, h4, h5, h6 {
overflow-wrap: break-word;
}
In CSS, text will automatically line-wrap if there isn't enough space to fit all of the characters on a single line.
在 CSS 中,如果没有足够的空间将所有字符放在一行上,文本将自动换行。
By default, the algorithm will look for “soft wrap” opportunities; these are the characters that the algorithm can split on. In English, the only soft wrap opportunities are whitespace and hyphens, but this varies from language to language.
默认情况下,算法会寻找“软换行”机会;这些是算法可以进行拆分的字符。在英语中,唯一的软换行机会是空格和连字符,但这因语言而异。
If a line doesn't have any soft wrap opportunities, and it doesn't fit, it will cause the text to overflow:
如果一行没有任何软换行的机会,并且它不适合,就会导致文本溢出:
Code Playground 代码游乐场
Result 结果
This can cause some annoying layout issues. Here, it adds a horizontal scrollbar. In other situations, it might cause text to overlap other elements, or slip behind an image/video.
这可能会导致一些恼人的布局问题。在这里,它添加了一个水平滚动条。在其他情况下,它可能会导致文本与其他元素重叠,或滑到图像/视频后面。
The overflow-wrap
property lets us tweak the line-wrapping algorithm, and give it permission to use hard wraps when no soft wrap opportunties can be found:
overflow-wrap
属性允许我们调整换行算法,并在找不到软换行机会时允许使用硬换行:
Code Playground
Result
Neither solution is perfect, but at least hard wrapping won't mess with the layout!
两种解决方案都不完美,但至少硬换行不会影响布局!
Thanks to Sophie Alpert for suggesting a similar rule! She suggests applying it to all elements, which is probably a good idea, but not something I've personally tested.
感谢索菲·阿尔珀特提出类似的规则!她建议将其应用于所有元素,这可能是个好主意,但我个人并没有进行过测试。
You can also try adding the hyphens
property:
您还可以尝试添加 hyphens
属性:
p {
overflow-wrap: break-word;
hyphens: auto;
}
hyphens: auto
uses hyphens (in languages that support them) to indicate hard wraps. It also makes hard wraps much more common.
hyphens: auto
使用连字符(在支持它们的语言中)来表示硬换行。这也使得硬换行变得更加常见。
It can be worthwhile if you have very-narrow columns of text, but it can also be a bit distracting. I chose not to include it in the reset, but it's worth experimenting with!
如果你的文本列非常窄,这可能是值得的,但它也可能有点分散注意力。我选择不在重置中包含它,但值得尝试!
Link to this heading8. Improve line wrapping 8. 改善换行
When there are too many words to fit on a single line of text, the default behaviour is to push any words that don’t fit onto the next line. This process is repeated until none of the lines overflow:
This algorithm works well enough most of the time, but it sometimes produces awkward results. My least favourite example is when a paragraph ends with an emoji, and that emoji is pushed to its own line:

To solve this problem, we can opt into an alternative line-wrapping algorithm with the new text-wrap
property!
要解决这个问题,我们可以选择使用新的 text-wrap
属性的替代换行算法!
For paragraphs, I use the pretty
option. This algorithm will make sure that the final line of text has at least two words. It also makes other subtle tweaks to improve the visual balance of the paragraph:
对于段落,我使用 pretty
选项。该算法将确保文本的最后一行至少有两个单词。它还会进行其他微妙的调整,以改善段落的视觉平衡:

For headings, I use the balance
option. This has a much stronger effect; the algorithm tries to make each line of text roughly the same length. This tends to make two-line headings feel a lot more balanced.
对于标题,我使用 balance
选项。这有更强的效果;算法试图使每行文本大致相同长度。这往往使两行标题感觉更加平衡。
This won’t always be what we want, but the point of a CSS reset is to set better baseline styles. We can always overwrite this property for any particular heading or paragraph.
这并不总是我们想要的,但 CSS 重置的目的是设置更好的基础样式。我们可以随时为任何特定的标题或段落覆盖此属性。
Browser support for text-wrap
varies by property; as I write this in November 2024, pretty
is at 72% support(opens in new tab) and balance
is at 87% support(opens in new tab). These numbers aren’t great, but it doesn’t really matter; as I shared in a recent blog post, we don’t need to worry about browser support for progressive enhancements like this.
对 text-wrap
的浏览器支持因属性而异;在我写这篇文章时(2024 年 11 月), pretty
的支持率为 72%,而 balance
的支持率为 87%。这些数字并不理想,但这并不重要;正如我在最近的博客文章中分享的,我们不需要担心像这样的渐进增强的浏览器支持。
Link to this heading9. Root stacking context
#root, #__next {
isolation: isolate;
}
This last one is optional. It's generally only needed if you use a JS framework like React.
As we saw in “What The Heck, z-index??”(opens in new tab), the isolation
property allows us to create a new stacking context without needing to set a z-index
.
This is beneficial since it allows us to guarantee that certain high-priority elements (modals, dropdowns, tooltips) will always show up above the other elements in our application. No weird stacking context bugs, no z-index arms race.
You should tweak the selector to match your framework. We want to select the top-level element that your application is rendered within. For example, create-react-app uses a <div id="root">
, so the correct selector is #root
.
Link to this headingOur finished product
Here's the CSS reset again, in a condensed copy-friendly format:
/*
Josh's Custom CSS Reset
https://www.joshwcomeau.com/css/custom-css-reset/
*/
*, *::before, *::after {
box-sizing: border-box;
}
* {
margin: 0;
}
body {
line-height: 1.5;
-webkit-font-smoothing: antialiased;
}
img, picture, video, canvas, svg {
display: block;
max-width: 100%;
}
input, button, textarea, select {
font: inherit;
}
p, h1, h2, h3, h4, h5, h6 {
overflow-wrap: break-word;
}
p {
text-wrap: pretty;
}
h1, h2, h3, h4, h5, h6 {
text-wrap: balance;
}
#root, #__next {
isolation: isolate;
}
Feel free to copy/paste this into your own projects! It's released without any restrictions, into the public domain (though if you wanted to keep the link to this blog post, I'd appreciate it!).
I chose not to release this CSS reset as an NPM package because I feel like you should own your reset. Bring this into your project, and tweak it over time as you learn new things or discover new tricks. You can always make your own NPM package to facilitate reuse across your projects, if you want. Just keep in mind: you own this code, and it should grow along with you.
Thanks to Andy Bell for sharing his Modern CSS Reset(opens in new tab). It helped tune some of my thinking, and inspired this blog post!
Link to this headingGoing deeper
My CSS reset is quite short (only 11 declarations!), and yet I've managed to spend an entire blog post talking about them. And honestly, there's so much more I want to say! We only scratched the surface in a bunch of places.
CSS is a deceptively complex language. Unless you pop the hood and learn what's really going on under there, the language will always feel a bit unpredictable and inconsistent. When your mental model is incomplete, you're bound to run into some problems.
If you take a bit of time to learn how the language really works, though, everything becomes so much more intuitive and predictable. I love writing CSS these days!
如果你花一点时间去了解语言是如何运作的,那么一切就会变得更加直观和可预测。如今我喜欢写 CSS!
For the past year and a half, I've been focused on helping JS developers change their relationship with CSS. I recently launched a comprehensive, interactive online course called CSS for JavaScript Developers(opens in new tab)
在过去一年半的时间里,我一直专注于帮助 JS 开发者改变他们与 CSS 的关系。我最近推出了一门全面的互动在线课程,名为《JavaScript 开发者的 CSS》。.
If you wish you were one of those people who liked/understood CSS, I made this course specifically for you!
如果你希望自己是那些喜欢/理解 CSS 的人,我专门为你制作了这个课程!
You can learn more here:
您可以在这里了解更多信息:
Link to this headingChangelog 更新日志
- June 2023 — I removed the
height: 100%
fromhtml
andbody
. This rule was added to make it possible to use percentage-based heights within the application. Now that dynamic viewport units(opens in new tab) are well-supported, however, this hacky fix is no longer required.
得到了很好的支持,但这个临时修复不再需要。 - October 2024 — I added #8, improved line wrapping with
text-wrap
.
2024 年 10 月 — 我添加了#8,改进了使用text-wrap
的换行。
Last updated on 最后更新于
November 25th, 2024 2024 年 11 月 25 日