AO3 Allowed CSS Properties: All 182, and Why Skins Fail
AO3 checks every CSS declaration when you save a site skin or work skin. One property it does not accept is enough to stop the whole skin from saving; it does not simply ignore that line. The quickest fix is to find the rejected property, replace it with an older or simpler equivalent, and check the value as well as the property name.
Below is the complete list currently used by AO3: 182 exact properties and 20 shorthand entries. Search for a property to get an immediate result, or sort either table alphabetically.
Short answer: If a property is missing from both lists, AO3 will usually reject it. There are two complications: shorthand entries match longer property names, and valid custom properties can be used in site skins but not work skins. Both are explained below.
Why one bad line rejects the whole skin
AO3 cleans user-submitted CSS before storing it. For every declaration, it checks the property name and then sanitizes the value. An unknown property produces an error. A value the sanitizer cannot parse produces another error. If cleaning removes every declaration from a rule, that empty rule produces an error too.
Because those errors make the skin invalid, this fails at save time:
#workskin .message-list {
display: flex;
gap: 1rem;
}
display is allowed. gap is not. The allowed line does not rescue the rejected one, so replace the gap rather than expecting AO3 to skip it:
#workskin .message + .message {
margin-top: 1rem;
}
AO3’s error can identify the property, but a long pasted skin may contain that name more than once. Search the CSS for the exact property followed by a colon—for example, gap:—and check every occurrence.
The complete AO3 CSS allowlist
The first table is AO3’s exact SUPPORTED_CSS_PROPERTIES list. “Allowed” means AO3’s cleaner accepts the property name. It does not promise that every possible value is accepted, that every browser still implements the property, or that the property will visibly affect AO3’s HTML.
202 entries shown
-replace |
-use-link-source |
accelerator |
accent-color |
aspect-ratio |
align-content |
align-items |
align-self |
alignment-adjust |
alignment-baseline |
appearance |
azimuth |
baseline-shift |
behavior |
binding |
bookmark-label |
bookmark-level |
bookmark-target |
bottom |
box-align |
box-direction |
box-flex |
box-flex-group |
box-lines |
box-orient |
box-pack |
box-shadow |
box-sizing |
caption-side |
clear |
clip |
color |
color-profile |
color-scheme |
content |
counter-increment |
counter-reset |
crop |
cue |
cue-after |
cue-before |
cursor |
direction |
display |
dominant-baseline |
drop-initial-after-adjust |
drop-initial-after-align |
drop-initial-before-adjust |
drop-initial-before-align |
drop-initial-size |
drop-initial-value |
elevation |
empty-cells |
fill |
filter |
fit |
fit-position |
float |
float-offset |
font |
font-effect |
font-emphasize |
font-emphasize-position |
font-emphasize-style |
font-family |
font-size |
font-size-adjust |
font-smooth |
font-stretch |
font-style |
font-variant |
font-weight |
grid-columns |
grid-rows |
hanging-punctuation |
height |
hyphenate-after |
hyphenate-before |
hyphenate-character |
hyphenate-lines |
hyphenate-resource |
hyphens |
icon |
image-orientation |
image-resolution |
ime-mode |
include-source |
inline-box-align |
justify-content |
layout-flow |
left |
letter-spacing |
line-break |
line-height |
line-stacking |
line-stacking-ruby |
line-stacking-shift |
line-stacking-strategy |
mark |
mark-after |
mark-before |
marks |
marquee-direction |
marquee-play-count |
marquee-speed |
marquee-style |
max-height |
max-width |
min-height |
min-width |
move-to |
nav-down |
nav-index |
nav-left |
nav-right |
nav-up |
opacity |
order |
orphans |
page |
page-policy |
phonemes |
pitch |
pitch-range |
play-during |
position |
presentation-level |
punctuation-trim |
quotes |
rendering-intent |
resize |
rest |
rest-after |
rest-before |
richness |
right |
rotation |
rotation-point |
ruby-align |
ruby-overhang |
ruby-position |
ruby-span |
size |
speak |
speak-header |
speak-numeral |
speak-punctuation |
speech-rate |
stress |
string-set |
stroke |
stroke-width |
tab-side |
table-layout |
target |
target-name |
target-new |
target-position |
top |
unicode-bibi |
unicode-bidi |
user-select |
vertical-align |
visibility |
voice-balance |
voice-duration |
voice-family |
voice-pitch |
voice-pitch-range |
voice-rate |
voice-stress |
voice-volume |
volume |
white-space |
white-space-collapse |
widows |
width |
word-break |
word-spacing |
word-wrap |
writing-mode |
z-index |
These are exact entries. Some also appear in the shorthand table because AO3 applies both checks.
background | background-color, background-image |
border | border-radius, border-top |
column | column-gap, grid-template-columns |
cue | cue-after, cue-before |
flex | flex-direction, flex-shrink |
font | font-family, font-size |
layer-background | layer-background-color |
layout-grid | layout-grid-mode |
list-style | list-style-image, list-style-type |
margin | margin-bottom, margin-inline |
marker | marker-offset |
outline | outline-color, outline-width |
overflow | overflow-wrap, overflow-y |
padding | padding-left, padding-top |
page-break | page-break-after, page-break-inside |
pause | pause-after, pause-before |
scrollbar | scrollbar-color, scrollbar-width |
text | text-align, text-decoration |
transform | transform-origin |
transition | transition-duration |
Examples show the property-name match only. AO3 still checks each declaration’s value.
No printed row contains that text. Check the match result above: a shorthand can accept a longer property name even when that full name is not printed in either table.
Why the shorthand list behaves differently
The 20 shorthand entries are not merely 20 extra property names. AO3 checks whether a submitted property name contains one of those strings. That is why border also admits border-radius and border-bottom-left-radius, while text admits familiar names such as text-align and text-decoration.
The match is unusually broad. For example, grid-template-columns gets through the property-name check because the name contains column, even though grid-template-columns is not one of the 182 exact entries. Bare gap contains no shorthand entry and fails, while column-gap contains column and passes the name check.
A shorthand match does not guarantee that the declaration will work. AO3 checks its value next. Treat the shorthand list as an answer to “will this property name get past the first gate?”, not as a browser-support table.
Vendor-prefixed forms can also pass when they contain one of AO3’s accepted prefixes—moz, ms, o, or webkit—and a supported property. Prefer the ordinary property unless a prefixed version solves a real compatibility problem.
What about CSS variables?
There is an important site-skin/work-skin split. A well-formed custom property name such as --page-bg can pass the shared CSS cleaner for a site skin. The name must begin with two hyphens and then contain only letters, digits, hyphens, or underscores.
Work skins are stricter. They reject custom-property declarations and var() references, even though the same syntax can be accepted in a site skin. If code using variables will not save as a work skin, resolve every variable to its literal value:
/* Site-skin pattern — do not paste this into a work skin */
:root {
--page-bg: #f7f2e8;
}
body {
background-color: var(--page-bg);
}
For a work skin, use:
#workskin .page {
background-color: #f7f2e8;
}
Custom properties are not included in the 182-property table: they follow a naming rule rather than a finite list, and they are not available in both kinds of skin.
Common rejected properties and what to use instead
| Rejected CSS | Why it fails | Practical replacement |
|---|---|---|
gap and row-gap | Neither name is on the exact list or matched by a shorthand. | Put a margin on adjacent items. column-gap passes the name check, but is not a general replacement for every layout. |
animation | The property is not accepted. | Keep the final state as static CSS. Remove animation declarations rather than leaving a broken effect. |
pointer-events | The property is not accepted. | Avoid designs that depend on click-through overlays. Rework the stacking or remove the overlay. |
backdrop-filter | Only filter appears in the exact list; backdrop-filter is a different name. | Use a solid or semi-transparent background color, plus a border or shadow if needed. |
aspect-ratio | The property is not accepted. | Set explicit dimensions, use max-width with height: auto, or use a percentage-padding wrapper when appropriate. |
inset | The property is not accepted. | Write top, right, bottom, and left separately. All four are in the exact list. |
Do not replace an unsupported property with the nearest-looking property unless it creates the same layout. filter, for example, does not reproduce backdrop-filter: one affects the element, while the other affects what is behind it.
The property may be allowed while the value is not
Checking the property list is only the first half of debugging. AO3 also restricts values and CSS structures. These are frequent failures:
calc(),clamp()andcolor-mix(): resolve them to literal numbers or colors before pasting.- Web fonts and
@font-face: AO3 rejects@font-face. Use a web-safefont-familystack. @mediablocks: do not paste them into the CSS field. AO3 represents media as skin metadata rather than preserving an@mediablock in submitted skin CSS.- Image URLs: an allowed property such as
background-imagecan still fail if its URL is not in a form AO3 accepts. A normal HTTPS address ending in.jpg,.jpeg,.png, or.gifis the safest shape; query strings and WebP are common problems. - The
fontshorthand: the property name is allowed, but a quoted font family can fail in its value. Writefont-family,font-size,font-style, andfont-weightseparately. content: use one quoted string,none, or an accepted image URL. An unquoted word orvar()can be rejected.
When a skin is rejected, debug in this order:
- Search for property names that appear in neither table.
- Check for custom properties or
var()in a work skin. - Check functions in values, especially
calc(),clamp()andcolor-mix(). - Check
@font-faceand@media. - Check image addresses and font-family values.
- Save again, then read the CSS back from AO3 to confirm the stored version contains the rules you expected.
Changing several things at once makes the result harder to diagnose. Fix the first reported problem, save again, and continue until AO3 accepts the skin.
Check it before you paste
The AO3 Skin Generator app checks the CSS it generates against AO3’s property list and related value rules before you copy it. Its site-skin builder has 16 starting templates; its work-skin export covers iMessage, WhatsApp, Twitter/X, and Google-style pages. See the WordFokus AO3 Skin Generator guide for an overview of every feature.
If you write a skin by hand, keep this page open as the reference. Browser CSS documentation tells you what a browser supports; this table tells you what AO3 permits through its cleaner. You need both answers.
Limits of this reference
This list comes from the open-source OTW Archive code, not from a guess about modern CSS support. It was last checked against the upstream source on 9 August 2026. AO3 can change the allowlist, so use that date when comparing this page with a later error.
An allowed property can still be rejected because of its value. It can also save successfully and do nothing because a browser does not implement it or because its selector matches no AO3 element. This reference does not explain why the OTW selected these properties; the source does not give us that answer.
The generator and this article are unofficial and are not affiliated with the OTW or Archive of Our Own.
