WordFokus branding with logo and website screenshot.

AO3 Twitter work skin: copy-paste CSS that actually works

You need two things to put a convincing tweet into an AO3 work as real text: a work skin containing the CSS, and a block of HTML inside the chapter. The complete working pair is on this page. Copy the CSS once, select that skin on the work, then paste the HTML wherever the tweet should appear.

If you want the quickest route: open the AO3 Skin Generator, choose X / Twitter, build the post, and click Work skin. The rest of this guide explains exactly what the generated CSS and HTML contain, how to install them, and where the limits are. For an overview of every generator feature, see the AO3 Skin Generator guide.

Why you need both CSS and HTML

The work skin is the reusable design: card borders, spacing, colors, typography, verified-badge shape and metric layout. The chapter HTML is the individual post: this account name, this handle, this sentence and these numbers. CSS without the HTML has nothing to style. HTML without the selected skin remains readable, but it will look like ordinary text rather than a tweet.

You normally save the CSS once and reuse it across a whole fic. Each new fictional post then needs only its generated HTML in the relevant chapter. If you choose the master work skin, the same saved skin can also style later iMessage, WhatsApp and Google blocks; you do not create a second skin for each platform.

This approach differs from inserting a screenshot. The text remains part of the chapter, can reflow on a narrow screen, can be selected, and still has a meaningful reading order when a reader disables Creator’s Style. It takes more setup the first time, but does not force every reader to zoom into a fixed-width image.

Fictional tweet rendered with an AO3 Twitter work skin in light mode
The worked Nora Vale tweet in light mode.
The same fictional AO3 Twitter work-skin example in dark mode
The identical content in dark mode, so only the theme changes.

How to install the Twitter work skin

  1. Sign in to AO3 and open Dashboard → Skins.
  2. Open My Work Skins, then choose Create Work Skin.
  3. Give it a unique title you will recognise, such as yourname — Twitter posts.
  4. Paste the complete CSS from the next section into the CSS field and save.
  5. Edit your work. In its Associations section, choose the new skin under Select Work Skin.
  6. Open the chapter editor’s HTML mode and paste the worked HTML where the tweet should appear.
  7. Preview the chapter with Creator’s Style on and off before posting.

Use WordPress-style rich text nowhere in this process: the CSS belongs in AO3’s work-skin CSS field, and the tweet markup belongs in the chapter’s HTML mode. Pasting either sample into AO3’s Rich Text editor can change the markup.

Complete copy-paste CSS

This is the exact light-theme CSS produced for the worked example above. It has passed the same AO3 allowlist and value checks used by the generator. Copy the whole block; a partial stylesheet will leave the tweet half styled.

AO3 Twitter work-skin CSS
#workskin .chat{width:34.375em;max-width:100%;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;margin:0 auto;box-sizing:border-box;}
#workskin .chat p{margin:0;padding:0;}
#workskin .tweets .tweet{background:#fff;border:1px solid #eff3f4;border-radius:1em;padding:1em;margin:0 0 0.75em 0;position:relative;box-sizing:border-box;transition:background-color 0.2s;}
#workskin .tweets .tweet:hover{background:#f7f9f9;}
#workskin .tweets .tweet.reply{margin-left:2.75em;margin-top:-0.5em;}
#workskin .tweets .tweet.reply::before{content:'';position:absolute;left:-2em;top:-0.5em;bottom:0.75em;width:2px;background:#cfd9de;}
#workskin .tweets .tweet.reply::after{content:'';position:absolute;left:-2em;top:1.25em;width:1.25em;height:2px;background:#cfd9de;}
#workskin .tweet-header{overflow:hidden;margin-bottom:0.75em;position:relative;}
#workskin .tweet img.avatar{width:2.5em;height:2.5em;border-radius:50%;float:left;margin-right:0.75em;}
#workskin .tweet .head{overflow:hidden;}
#workskin .tweet .head-content{display:block;}
#workskin .tweet .name-line{line-height:1.25;}
#workskin .tweet .name-line .name,#workskin .tweet .name-line .verified-container,#workskin .tweet .name-line .handle,#workskin .tweet .name-line .follow-dot,#workskin .tweet .name-line .follow-btn,#workskin .tweet .name-line .twitter-logo{display:inline-block;vertical-align:middle;margin-right:0.25em;}
#workskin .tweet .name-line .twitter-logo{margin-right:0;}
#workskin .tweet .name{font-weight:700;color:#0f1419;font-size:0.938em;line-height:1.333;white-space:nowrap;}
#workskin .tweet .verified-container{display:inline-block;vertical-align:middle;}
#workskin .tweet .verified-badge{display:inline-block;vertical-align:middle;font-size:0.688em;width:1.636em;line-height:1;padding:0.318em 0;text-align:center;font-weight:700;color:#fff;background:#1d9bf0;border-radius:50%;}
#workskin .tweet .handle{color:#71767b;font-weight:400;font-size:0.938em;line-height:1.333;white-space:nowrap;}
#workskin .tweet .follow-dot{color:#71767b;font-size:0.938em;line-height:1.333;}
#workskin .tweet .follow-btn{background:transparent;color:#1d9bf0;font-weight:700;font-size:0.938em;padding:0;border:none;cursor:pointer;line-height:1.333;flex-shrink:0;white-space:nowrap;}
#workskin .tweet .follow-btn:hover{color:#1a8cd8;text-decoration:underline;}
#workskin .tweet .twitter-logo{width:1.25em;height:1.25em;display:inline-block;vertical-align:middle;}
#workskin .tweet .body{margin-top:0.8em;font-size:0.938em;line-height:1.333;color:#0f1419;word-wrap:break-word;white-space:pre-wrap;}
#workskin .tweet .body .hashtag{color:#1d9bf0;font-weight:400;}
#workskin .tweet .body .mention{color:#1d9bf0;font-weight:400;}
#workskin .tweet .tweet-image{width:100%;max-width:100%;height:auto;max-height:17.813em;border-radius:1em;margin-top:0.75em;border:1px solid #eff3f4;display:block;}
#workskin .tweet .time-line{margin-top:1.067em;font-size:0.938em;color:#536471;padding-bottom:1.067em;border-bottom:1px solid #eff3f4;}
#workskin .tweet.no-metrics .time-line{padding-bottom:0;border-bottom:none;}
#workskin .tweet .metrics{display:flex;justify-content:space-between;padding:0.857em 0;font-size:0.875em;color:#536471;border-bottom:1px solid #eff3f4;width:100%;}
#workskin .tweet .metrics p{display:contents;}
#workskin .tweet .metric{display:inline-block;cursor:pointer;transition:color 0.2s;margin-right:1.286em;}
#workskin .tweet .metric:first-child{justify-content:flex-start;}
#workskin .tweet .metric:last-child{margin-right:0;}
#workskin .tweet .metric-icon{width:1.429em;height:1.429em;display:inline-block;vertical-align:middle;margin-right:0.429em;}
#workskin .tweet .glyph-icon{font-size:1.286em;line-height:1;margin-right:0.222em;vertical-align:-0.111em;}
#workskin .tweet .metric-count{color:#536471;font-weight:400;font-size:1em;line-height:1.429;vertical-align:middle;}
#workskin .tweet .metric:hover .metric-count{text-decoration:underline;}
#workskin .tweet .metric.replies:hover{color:#1d9bf0;}
#workskin .tweet .metric.replies:hover .metric-icon{opacity:1;filter:none;}
#workskin .tweet .metric.retweets:hover{color:#00ba7c;}
#workskin .tweet .metric.retweets:hover .metric-icon{opacity:1;filter:none;}
#workskin .tweet .metric.likes:hover{color:#f91880;}
#workskin .tweet .metric.likes:hover .metric-icon{opacity:1;filter:none;}
#workskin .tweet .metric.bookmarks:hover{color:#1d9bf0;}
#workskin .tweet .metric.bookmarks:hover .metric-icon{opacity:1;filter:none;}
#workskin .tweet .metric.views:hover{color:#536471;}
#workskin .tweet .metric.views:hover .metric-icon{opacity:1;}
#workskin .tweet .replying-to{font-size:0.813em;color:#536471;margin:0.615em 0 0.308em 0;line-height:1.231;}
#workskin .tweet .replying-to .reply-handle{color:#1d9bf0;text-decoration:none;}
#workskin .tweet .replying-to .reply-handle:hover{text-decoration:underline;}
#workskin .tweet.expanded{padding:1em;display:flex;margin-left:-0.75em;}
#workskin .tweet.expanded > *{margin-left:0.75em;}
#workskin .tweet.expanded .avatar{width:2.5em;height:2.5em;flex-shrink:0;margin:0;}
#workskin .tweet.expanded .expanded-content{flex:1;min-width:0;}
#workskin .tweet.expanded .expanded-name{display:flex;align-items:center;margin-bottom:0.125em;margin-left:-0.25em;}
#workskin .tweet.expanded .expanded-name p{display:contents;}
#workskin .tweet.expanded .expanded-name .name,#workskin .tweet.expanded .expanded-name .verified-container{margin-left:0.25em;}
#workskin .tweet.expanded .expanded-name .name{font-weight:700;color:#0f1419;font-size:1.25em;line-height:1.2;}
#workskin .tweet.expanded .expanded-name .verified-badge{font-size:0.764em;}
#workskin .tweet.expanded .expanded-handle{color:#536471;font-size:0.938em;line-height:1.333;margin-bottom:0.267em;}
#workskin .tweet.expanded .replying-to{margin:0 0 0.923em 0;}
#workskin .tweet.expanded .expanded-body{font-size:1.438em;line-height:1.217;color:#0f1419;word-wrap:break-word;white-space:pre-wrap;}
#workskin .tweet.expanded .tweet-image{margin-top:1em;}
#workskin .tweet.expanded .time-line{border:none;padding:0;margin-top:1.067em;}
#workskin .tweet .quote{border:1px solid #eff3f4;border-radius:0.75em;padding:0.75em;margin-top:0.75em;transition:background-color 0.2s;cursor:pointer;}
#workskin .tweet .quote:hover{background:#f7f9f9;}
#workskin .tweet .quote-head{display:flex;align-items:center;font-size:0.875em;line-height:1.143;margin-bottom:0.286em;margin-left:-0.286em;}
#workskin .tweet .quote-head p{display:contents;}
#workskin .tweet .quote-head .quote-avatar,#workskin .tweet .quote-head .quote-name,#workskin .tweet .quote-head .quote-verified-container,#workskin .tweet .quote-head .quote-handle{margin-left:0.286em;}
#workskin .tweet .quote-name{font-weight:700;color:#0f1419;}
#workskin .tweet .quote-avatar{width:1.429em;height:1.429em;border-radius:50%;overflow:hidden;}
#workskin .tweet .quote-verified-container{display:inline-flex;align-items:center;margin-left:-0.143em;}
#workskin .tweet .quote-verified-container > *{margin-left:0.143em;}
#workskin .tweet .quote-verified-badge{display:inline-block;vertical-align:middle;font-size:0.699em;width:1.636em;line-height:1;padding:0.318em 0;text-align:center;font-weight:700;color:#fff;background:#1d9bf0;border-radius:50%;}
#workskin .tweet .quote-handle{color:#536471;font-weight:400;font-size:1em;}
#workskin .tweet .quote-body{margin-top:0.267em;font-size:0.938em;line-height:1.333;color:#0f1419;}
#workskin .tweet .quote-image{width:100%;height:auto;border-radius:0.75em;margin-top:0.75em;border:1px solid #eff3f4;}
#workskin .tweets .wm{margin-top:1.2em;font-size:0.563em;opacity:0.45;text-align:center;color:#536471;}
#workskin .link{text-decoration:none;color:#1d9bf0;}
#workskin .visually-hidden{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0;}

Worked HTML for one tweet

The HTML below matches the Nora Vale example: avatar, CSS-drawn verified badge, handle, timestamp and engagement metrics. Paste it in the chapter’s HTML mode after the work skin has been selected.

Chapter HTML
<div class="chat twitter theme-light tweets"><div class="tweet"><div class="tweet-header"><img src="https://media.publit.io/file/AO3-Skins-App/avatars/alt-girl.png" alt="Avatar" class="avatar" width="40" height="40" /><div class="head"><div class="head-content"><div class="name-line"><b class="name">Nora Vale</b> <span class="verified-container"><span class="verified-badge" title="Verified">✔</span></span> <span class="visually-hidden"> (</span><span class="handle">@noravale</span><span class="visually-hidden">) </span><span class="follow-dot">·</span><span class="visually-hidden"> </span><span class="follow-btn">Follow</span><img src="https://media.publit.io/file/AO3-Skins-App/twitter-logo.png" alt="" class="twitter-logo" width="20" height="20" /></div></div></div></div><span class="visually-hidden"> tweeted: </span><div class="body">The archive was locked from the inside. The dust on the windowsill was still warm.</div><div class="time-line">9:42 PM · Oct 31, 2025</div><div class="metrics"><span class="metric replies" title="Replies"><span class="glyph-icon">↩︎</span> <span class="metric-count">24</span><span class="visually-hidden"> replies</span></span> <span class="metric retweets" title="Retweets"><span class="glyph-icon">⇄</span> <span class="metric-count">318</span><span class="visually-hidden"> retweets</span></span> <span class="metric likes" title="Likes"><span class="glyph-icon">♡</span> <span class="metric-count">2.4K</span><span class="visually-hidden"> likes</span></span> <span class="metric views" title="Views"><img src="https://media.publit.io/file/AO3-Skins-App/twitter-views.png" alt="" class="metric-icon" width="20" height="20" /> <span class="metric-count">51.4K</span><span class="visually-hidden"> views</span></span></div></div><div class="wm">made by wordfokus.com/ao3skingen</div></div>

To make another post, it is safer to generate fresh HTML than to hand-edit this wall of nested elements. If you do edit it, change the visible name, handle, body, timestamp and metric counts without removing classes or the hidden accessibility text.

What is safe to edit by hand?

Plain text between tags is the safest part to change. You can replace Nora Vale, @noravale, the post body and the timestamp. You can also replace the avatar URL with another full HTTPS image address. Keep the surrounding elements and class names intact: names such as tweet, body, metrics and verified-badge are how the stylesheet finds each part.

Metric counts contain both a visible number and a visually hidden word. If you change 24 replies to 31, leave the hidden word replies in place. It is what makes the unstyled version say “31 replies” instead of presenting an unexplained number.

Why this survives AO3’s formatter

AO3 does not store submitted HTML untouched. Inline content can be wrapped in paragraphs, which is enough to break a fragile flex layout. This export uses two defences together:

  1. The generated HTML is one continuous line. There is no indentation whitespace between the structural elements for the editor to reinterpret.
  2. The CSS neutralises injected paragraphs. The opening rules include #workskin .chat p { margin: 0; padding: 0; }, with targeted display: contents rules where an injected paragraph would otherwise become a layout item.

The avatar also uses a float with an overflow-established block beside it. That is deliberate: it keeps the header stable when AO3 inserts a paragraph around the inline name and handle.

The verified badge is CSS, not an image

The blue verified badge is a checkmark inside a circle drawn by CSS. It is not fetched from an image host. That means the badge still exists if an external host changes its URLs, a reader blocks images, or the post is viewed years later.

The reply, repost and like symbols are text glyphs for the same reason. The X logo and optional views/bookmark icons are still external images because they are genuinely iconographic. The export therefore reduces its image-host dependency rather than pretending it has none.

This is also why some repetitive CSS is normal in AO3 skins. Inline style values and work-skin variables are unavailable, so data that varies per post sometimes has to become classes. One published Twitter skin uses .p1 through .p100 for poll widths. This generator uses the same AO3-compatible idea for its poll bars: percentage values become classes from .poll-pct-0 through .poll-pct-100 in five-point steps. Patterns like this explain why generated work-skin CSS can be longer than ordinary website CSS.

Light, dim and dark mode — and one skin per fic

The generator can export Twitter posts in light, dim or dark mode. For a fic that may contain several platforms or themes, choose the All four platforms work-skin option. That master skin covers iMessage, WhatsApp, Twitter/X and Google in every theme each platform supports.

This matters because AO3 lets a work use exactly one work skin. Selecting another skin does not stack the two styles; it replaces the first selection. If your fic already has a custom work skin, combine the CSS deliberately or use the generator’s master skin as the starting point.

ChoiceUse it when
Just Twitter/XThe work only needs this platform and the selected theme.
All four platformsThe work may use different apps or themes in later chapters.
Existing custom skinMerge the Twitter CSS into that one skin; do not expect AO3 to apply two skins.

If the other platform you need is a texting scene, the companion guide to AO3 iMessage and WhatsApp work skins has the complete CSS for both, and explains the details that separate them — where a reaction sits, how delivery state is shown, and why group name colours have to be classes rather than colours.

Thread mode: what it does and what it does not do

Three-part fictional Twitter thread connected by lines in the AO3 Skin Generator
Thread mode uses the reply relationships you choose to draw the connecting lines.

To build a thread from scratch: choose Reply in the composer and select the earlier post it answers. In Thread mode, each new post defaults to replying to the previous one, so a linear thread is quick to build.

To change an existing post, open it in the timeline and use Relationship to choose a different reply parent or turn it back into a new post. Replying-to handles can be filled automatically from the thread or entered manually.

The generator also creates static Twitter-style polls with two to four choices. Open polls show vote counts and time remaining; closed polls show percentages and a final-results label. Quote tweets, verified accounts, attached images, per-post metrics and visual thread lines are supported too.

What happens when Creator’s Style is off?

AO3 readers can disable a work’s custom styling. Downloads and some assistive reading paths also care far more about the chapter’s text order than its visual layout. A robust work skin therefore has to fail into readable prose rather than a pile of decorative symbols.

This example includes hidden punctuation and labels that become useful when the CSS is absent. The header reads as “Nora Vale (@noravale) tweeted,” and each engagement number keeps its noun: replies, retweets, likes and views. The credit remains visible in either mode, so an author can see it and remove it if they prefer to place attribution in their notes.

Preview both states before publishing. In the AO3 preview, view the styled work normally, then disable Creator’s Style. Check that the post body is still in the right place, the account is identifiable, and no bare number is left without a label. A skin that looks convincing but becomes nonsense when disabled is not finished.

Building it without editing code

AO3 Skin Generator editor beside a live preview of a fictional tweet
The editor changes the structured post and produces the CSS and HTML shown above.

The AO3 Skin Generator app is the primary route if you do not want to edit the sample by hand. It builds the post, checks the work-skin CSS against AO3’s rules, and gives you the two copies in the correct order. The WordFokus pillar guide explains the generator’s other screenshot, work-skin, and site-skin features.

If AO3 rejects CSS you have edited yourself, use the searchable complete AO3 CSS property reference to find the unsupported declaration.

Troubleshooting

The chapter shows plain text

First check that the work skin is selected on the work itself. Creating a skin does not automatically attach it to a fic. Edit the work, find Select Work Skin, choose the saved skin, save, and preview again. If the skin is selected, confirm that the chapter HTML still contains the outer chat twitter classes.

Only part of the tweet is styled

This usually means only part of the CSS or HTML was copied, or the markup was pasted through Rich Text mode. Copy the complete CSS block again, save the skin, switch the chapter editor to HTML mode, and replace the affected tweet with a fresh generated HTML block. Do not repair dozens of missing closing tags individually unless you are comfortable debugging HTML.

AO3 refuses to save the skin

The code printed on this page is generated and passes the project’s AO3 validator. If a modified version fails, undo the most recent additions first. Check the reported property against the AO3 CSS allowlist, then check its value: an allowed property can still fail because of calc(), a rejected image address, a web font or another unsupported value.

An avatar or icon is broken

Open the image address directly. AO3 does not store a backup of remote images. If the file moved or the host stopped serving it, upload a replacement somewhere durable and update every affected chapter. The main post text and CSS-drawn badge will survive; externally hosted avatars and remaining icon files will not.

Limits and checks before posting

  • The tool is unofficial and is not affiliated with the OTW or Archive of Our Own.
  • Thread mode draws the reply relationships you specify; it does not infer which unrelated posts should be connected.
  • Polls are visual, static fiction. Readers cannot cast a live vote in the AO3 chapter.
  • A work can select one work skin, not several stacked skins.
  • Avatars, the X logo and optional views/bookmark icons are externally hosted images. Keep copies of images you control.
  • Always preview with Creator’s Style both enabled and disabled. Without the skin, the hidden text should leave the post understandable rather than turning it into unexplained numbers.

Sources and further reading

Not sure this is the kind of skin you need? Compare AO3 work skins, site skins and public skins before installing anything.

Similar Posts