# Profile Skins Profile skins customize Bliish profile pages with sanitized HTML and passive CSS. Skins are edited in the profile editor under **Skin HTML** and shared from **Skins -> Submit skin**. The skin field is limited to 20,000 characters. Profile skins may be empty. Shared skins must save to non-empty sanitized skin HTML. Profile and shared skin forms sanitize skin HTML before saving. Profile rendering sanitizes profile skin HTML again before rendering. ## Quick Start Use one or more `

Optional custom skin HTML appears on the profile.

``` ## Rendering Model Skin CSS is collected from sanitized ` ``` ```html ``` ## CSS Selector Rules Every skin CSS selector must include at least one supported hook. Supported hooks are `[data-skin-page]`, `[data-skin-root]`, `[data-skin-version="2026"]`, and supported `[data-skin-part="..."]` values. Bare page selectors are limited to `[data-skin-page]` or `[data-skin-version="2026"]` by themselves. Use `[data-skin-root]` or `[data-skin-part="..."]` before descendant selectors. This is allowed: ```css [data-skin-part="bio"] a:hover { color: red; } ``` This is dropped because it is a descendant of the body hook rather than a profile part: ```css [data-skin-page] a { color: red; } ``` This is dropped because it has no skin hook: ```css body { color: red; } ``` Selectors with unsupported attribute selectors are dropped. Selectors using `:has()`, `:is()`, or `:where()` are dropped. Simple `:not(...)` selectors can survive when the `:not(...)` contents only use simple class, id, tag, or spacing characters. If any selector in a comma-separated selector list is unsafe, the whole rule is dropped. Selectors longer than 500 characters are dropped. The sanitizer removes `<` and `>` from CSS before parsing, so do not use child combinators. Use descendant selectors instead of `>`. ## CSS Declaration Rules Most ordinary CSS property names are accepted. The properties `behavior` and `-moz-binding` are blocked. `position:absolute`, `position:fixed`, and `position:sticky` are blocked. `position:relative` is not blocked by the skin sanitizer. CSS custom properties are accepted when the name starts with `--` and does not use a blocked app-owned prefix. CSS values are dropped if they contain CSS escapes, `expression(...)`, `javascript:`, `vbscript:`, `behavior:`, `-moz-binding`, `attr(...)`, `<`, or `>`. A declaration containing an unsafe `url(...)` is dropped. Other safe declarations in the same rule can still survive. CSS comments are removed. Malformed CSS rule blocks may be dropped. ## CSS At-Rules `@import` is only kept for `https://fonts.googleapis.com/...` stylesheets. Other `@import` rules are removed. `@media` rules can survive when the media prelude uses the sanitizer's supported simple characters. `@keyframes` rules can survive when the animation name is alphanumeric, underscore, or hyphen. Keyframe selectors can be `from`, `to`, or percentages. Other CSS at-rules are removed. ## URLs And Media Links and resources use different URL rules. Link `href` values can be local paths, hash links, `http:`, `https:`, or `mailto:`. Link `rel` is rewritten to `nofollow noopener noreferrer`. Link `target` is kept only when it is `_blank`. Image `src`, table `background`, and CSS `url(...)` resources can be local app paths or `https:` URLs. Resource URLs using `http:`, `data:`, `file:`, `javascript:`, or `vbscript:` are removed. Invalid image URLs are replaced with an empty ``. CSS declarations with invalid `url(...)` values are removed. ## Embeds Skins can include sanitized ` ``` The saved iframe source is normalized to `https://www.youtube-nocookie.com/embed/dQw4w9WgXcQ`. ## Common Reasons Code Disappears The selector did not include a supported skin hook. The selector used `body`, `.profile`, `nav`, `table`, or another global selector by itself. The selector used an unsupported attribute selector. The rule used a blocked at-rule. The declaration used an unsafe URL. The declaration used a blocked CSS value. The HTML tag or attribute is not in the allowed lists. The image or iframe URL did not match the sanitizer's URL rules. ## Developer Verification The skin hook list comes from `src/skins/contract.ts`. Skin rendering behavior comes from `src/skins/rendering.tsx`, `src/views/profile/page.tsx`, and `src/views/profile/main.tsx`. Shell hook behavior comes from `src/shell/layout.tsx`, `src/shell/nav.tsx`, and the layout primitives in `src/shell/page.tsx`. Profile part placement comes from `src/views/profile/layout.tsx`, `src/views/profile/sidebar.tsx`, `src/views/profile/main.tsx`, `src/views/profile/details.tsx`, `src/views/profile/actions.tsx`, `src/views/posts/panels.tsx`, and the split layout helpers in `src/shell/page.tsx`. Color and radius variables come from `src/skins/colorPalette.ts`, `src/skins/colorPaletteEditor.tsx`, and `public/static/css/features/profile.css`. HTML, CSS, URL, and iframe sanitizer behavior comes from `src/server/security/skinHtml.ts`, `src/server/security/css.ts`, `src/server/security/urls.ts`, and `src/server/security/embeds.ts`. Skin create, edit, preview, and apply behavior comes from `src/routes/skins/index.tsx`. Profile skin save and generated color-skin behavior comes from `src/routes/profile/index.tsx` and `src/routes/profile/edit/actions.ts`. When changing skin behavior, update this document in the same change.