Soft Gating with CSS
The widget toggles a data-fanid-authed attribute on <body> whenever the user's authentication state changes.
Everything else on the page can react to that attribute with plain CSS — no JavaScript hooks, no widget API calls,
and no Custom Element wrapping. Pages built this way stay fully cacheable on a CDN because the markup is identical
for signed-in and signed-out visitors; the only thing that changes is the attribute the widget flips at runtime.
This is additive to the rest of the gated-content toolkit. Use it alongside <wmt-fanid-protected>,
[data-fanid-gated], and the programmatic API — pick whichever fits each block.
How it works
When the widget initializes and after every login, logout, or cross-tab session change, it calls:
document.body.toggleAttribute('data-fanid-authed', isAuthenticated)
So during the lifecycle of a page:
- Before the widget initializes: no
data-fanid-authedattribute on<body> - After init when the user is signed out: no attribute
- After init when the user is signed in:
<body data-fanid-authed>
You drive the visibility of any element on the page by combining a marker attribute on the element with a body selector in CSS.
Basic pattern
Mark elements you want to hide from logged-out users with data-fanid-locked. Mark sign-in CTAs that should only
appear to logged-out users with data-fanid-locked-cta. Then add this stylesheet:
[data-fanid-locked] { display: none; }
body[data-fanid-authed] [data-fanid-locked] { display: block; }
body[data-fanid-authed] [data-fanid-locked-cta] { display: none; }
<article data-fanid-locked>
<h2>Subscriber-only deep dive</h2>
<p>Full article body…</p>
</article>
<aside data-fanid-locked-cta>
<p>Sign in with FanID to read this and 200+ other features.</p>
<button onclick="FanIDWidget.login()">Sign in</button>
</aside>
Logged-out visitors see the CTA and not the article. Logged-in visitors see the article and not the CTA. The HTML served by your origin is identical in both cases, so your CDN can keep caching it normally.
Why prefer it
- CDN-friendly. No per-user variation in the HTML. The same response is served to everyone, and the attribute is flipped client-side once auth resolves.
- No FOUC of premium content. Because the default rule is
display: none, locked content stays hidden until the widget confirms the user is signed in. There is no brief moment where premium content appears and then gets blurred away. - Composable. Any element with any selector pattern works. Use
data-fanid-lockedbecause it is short, or invent your own attribute if you want different visibility rules per block.
Reacting to specific states
Mirror the same pattern for any state-conditional UI:
/* Show navigation links only when logged in */
.nav__profile-link { display: none; }
body[data-fanid-authed] .nav__profile-link { display: inline-block; }
/* Show "sign in" only when logged out */
body[data-fanid-authed] .nav__signin-link { display: none; }
Combining with the visual gate
Soft gating with the body attribute removes content entirely. The <wmt-fanid-protected> element and
[data-fanid-gated] attribute keep content in the DOM and apply a visual blur, which is what makes it indexable by
search engines. Use them together:
- Soft-gate interactive widgets, navigation links, and account-only controls — things search engines do not need to see.
- Visual-gate article bodies and other content you want indexed — search engines see the raw content; humans see a blur with a sign-in prompt.
Caveats
- Soft-gated content is not in the rendered output for logged-out users, which means it is not indexed by search engines. If you care about SEO for the content, use the visual gate instead.
- The attribute toggles only after the widget initializes. The initial CSS rule (
display: none) is what keeps premium content hidden during the brief window between page load and widget init. - The selector relies on
body[data-fanid-authed]matching only when the attribute is present. If you are testing the styles with DevTools, remember that the boolean attribute has no value —body[data-fanid-authed=""]andbody[data-fanid-authed="true"]will not match.