Gated Content

Soft Gating with CSS

CSS-only content gating driven by the data-fanid-authed body attribute.

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-authed attribute 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-locked because 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=""] and body[data-fanid-authed="true"] will not match.
Copyright © 2026