OsirionCookieConsent
OsirionCookieConsent shows a fixed cookie banner until the visitor accepts, declines or saves their own choice. The
buttons post a form to an endpoint in your app, which stores the decision in a cookie and redirects back, so the
banner works under static SSR without script. Analytics trackers can then wait for that consent.
Package Osirion.Blazor.Core, namespace Osirion.Blazor.Components. Source on GitHub.
Working example
There is no live demo on this page, because the banner posts to an endpoint of the site that serves it and sets a
real cookie there, and a demo would change the consent state of every page. This site runs the component itself: the
banner at the bottom of the window on a first visit is <OsirionCookieConsent /> with its defaults. Choose Customize
there to see the category panel.
Setup
Register the services and map the endpoint in Program.cs:
using Osirion.Blazor.Core.Extensions;
builder.Services.AddOsirionCookieConsent();
var app = builder.Build();
app.UseAntiforgery();
app.MapOsirionCookieConsent(); // POST /api/cookie-consent
AddOsirionCookieConsent registers IHttpContextAccessor and the antiforgery services. The endpoint requires an
antiforgery token, so app.UseAntiforgery() must run before the endpoints, as in the Blazor Web App template.
Then place the banner in a statically rendered layout:
@using Osirion.Blazor.Components
<OsirionCookieConsent PolicyLink="/privacy" />
Usage
A banner with your own text and categories:
<OsirionCookieConsent Title="Cookies on this site"
Message="We use a cookie to remember your choice and, if you agree, privacy-friendly statistics."
PolicyLink="/privacy"
PolicyLinkText="Privacy policy"
ShowCustomizeButton="true"
Categories="categories"
ConsentExpiryDays="180" />
@code {
private readonly IReadOnlyList<CookieCategory> categories =
[
new CookieCategory { Id = "necessary", Name = "Necessary", Description = "Keeps the site working.", IsRequired = true, IsEnabled = true },
new CookieCategory { Id = "analytics", Name = "Statistics", Description = "Counts visits so we can improve the docs." }
];
}
Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
Title |
string |
"Cookie Consent" |
The banner heading (h3), which is also the accessible name of the banner region. |
Message |
string |
"We use cookies to improve your experience on our website. By continuing to browse, you agree to our use of cookies." |
The banner text, rendered as encoded text. |
AcceptButtonText |
string |
"Accept All" |
The text of the button that accepts every category. |
DeclineButtonText |
string |
"Decline" |
The text of the button that declines every optional category. |
CustomizeButtonText |
string |
"Customize" |
The text of the button that opens the category choices. |
SavePreferencesButtonText |
string |
"Save Preferences" |
The text of the button that saves the ticked categories. |
ShowDeclineButton |
bool |
true |
Whether the decline button is shown. |
ShowCustomizeButton |
bool |
true |
Whether the customize button is shown. |
ShowCustomizationPanel |
bool |
true |
Whether the category checkboxes are shown once the visitor chose to customize. When it is false, the customize button reloads the page without any choices. |
CustomizePanelTitle |
string |
"Cookie Preferences" |
The heading (h4) of the category panel. |
PolicyLink |
string? |
null |
The address of your privacy or cookie policy, shown as a link after the message. Null or empty, the default, shows no link. |
PolicyLinkText |
string |
"Learn more" |
The text of the policy link. |
Icon |
RenderFragment? |
null |
The markup shown beside the text. Null, the default, shows no icon. |
Categories |
IReadOnlyList<CookieCategory> |
- | The categories shown in the panel. Defaults to necessary (required), analytics, marketing and preferences. Accept All records only those four IDs, so a custom ID is recorded only when the visitor ticks it in the panel. |
ConsentEndpoint |
string |
"/api/cookie-consent" |
Where the form posts. Defaults to /api/cookie-consent, the pattern MapOsirionCookieConsent maps; change it only together with that call. |
Position |
string |
"bottom" |
The banner position, added as the class osirion-cookie-consent-{Position}. The stylesheet defines bottom (the default) and top. |
ConsentExpiryDays |
int |
365 |
The consent expiry in days. The value is posted with the form and applied to the consent cookie by the consent endpoint, which accepts 1 to 3650 days and uses 365 for anything else. |
Theme |
ThemeMode |
ThemeMode.System |
The component's color mode. Light or Dark writes it to data-theme (data-bs-theme under Bootstrap) on the root element; System writes nothing. |
Class |
string? |
null |
Extra CSS classes for the component's root element. |
Style |
string? |
null |
An inline style for the component's root element. |
CookieCategory has Id, Name, Description, IsRequired (always on, checkbox disabled) and IsEnabled
(checked by default). Class goes on the banner element; unmatched attributes go on the form.
Behavior
- Flow without script. Accept and Decline post the form, the endpoint writes the cookie and redirects to the
same page, and the banner is no longer rendered. Customize redirects to the page with
?customize-cookies=true, where the banner shows the category checkboxes; Save Preferences records the checked ones. - Changing the answer. A link to any page with
?customize-cookies=true, such as a "Cookie settings" link in the footer, reopens the panel after the visitor answered, with their saved choices checked; Save Preferences replaces the recorded consent, so withdrawing it takes the same few clicks as giving it. Arrives in the release after 4.1.0. - What is recorded. Accept sets
necessary,analytics,marketingandpreferencesto true; Decline setsnecessaryto true and the other three to false. Save Preferences recordsnecessaryplus each category the visitor checked; unchecked ones are left out, which counts as no consent. Custom category IDs are therefore recorded only through the panel. - The cookie.
osirion_cookie_consentholds JSON withVersion,ConsentDate,ConsentTypeandCategories. It isHttpOnly,SecureandSameSite=Lax, so serve the site over HTTPS. Script cannot read it; decisions are made on the server. - Security. The form carries an antiforgery token. A post without a valid token gets 400 and records nothing, and the endpoint redirects only to local paths.
- Rendering. The component reads the cookie from the current request. Without an HTTP request (an interactive
render with no
HttpContext), it renders nothing. The banner has no interactive mode. - Accessibility. The banner is a region named by its title, so its text is inside a landmark. The buttons are
real
buttonelements in a form, usable with the keyboard.
Reading consent on the server
CookieConsentHandler in Osirion.Blazor.Core.Handlers reads the cookie:
using Osirion.Blazor.Core.Handlers;
bool statistics = CookieConsentHandler.IsCategoryConsented(httpContext, "analytics");
CookieConsentData? consent = CookieConsentHandler.GetConsentData(httpContext);
ClearConsent(httpContext) deletes the cookie, for example on a "change cookie settings" endpoint.
app.UseOsirionCookieConsent() adds middleware that puts the parsed data in HttpContext.Items["CookieConsent"].
Gating analytics
The analytics module can render trackers only after the visitor accepted the analytics category. Turn the gate on
in code:
using Osirion.Blazor.Analytics.Extensions;
using Osirion.Blazor.Extensions;
builder.Services.AddOsirion(osirion => osirion
.UseAnalytics(analytics => analytics
.AddClarity(clarity => clarity.SiteId = "your-clarity-id")
.RequireConsent()));
or in configuration:
{
"Osirion": {
"Analytics": {
"Consent": { "RequireConsent": true }
}
}
}
Without consent no tracker script is rendered at all; with GA4, Google Consent Mode starts with its defaults set to
denied. RequireConsent(options => options.Category = "statistics") uses another category ID. See
Analytics.
Styling
The form has osirion-cookie-consent-form; the banner has osirion-cookie-consent and
osirion-cookie-consent-bottom or osirion-cookie-consent-top, plus osirion-cookie-consent-customizing while the
panel is open. The banner is position: fixed across the full width at the chosen edge. Inner parts include
osirion-cookie-consent-title, osirion-cookie-consent-description, osirion-cookie-consent-link,
osirion-cookie-consent-actions, osirion-cookie-consent-customize and osirion-cookie-consent-category. Colors
come from the theme tokens (--osirion-text-primary, --osirion-border-color, --osirion-action-primary and
others), and the buttons use the active CSS framework's classes.