Profile
A compact avatar + name combo for the trailing slot of a <x-wirekit::header> or the leading slot of a user menu. Profile is structural — it lays out an avatar image and a name side-by-side without imposing a link target or a click behavior. Wrap it in an <a>, a <button>, or a <x-wirekit::dropdown.trigger> when you need interaction.
Basic Usage
Pass avatar for an image, name for text, or both for a combined mark. The avatar is decorative (alt="") — the name text provides the accessible label when present.
Custom Content
If you need more than an avatar and name (e.g. a role badge, a status indicator, a chevron), pass arbitrary children. Children render after the avatar and name — they are additive, not replacements.
Owner
Props
| Prop | Type | Default | Description |
|---|---|---|---|
avatar |
string|array|null |
null |
URL of an avatar image, or ['src' => …, 'initials' => …, 'alt' => …]. Rendered at h-8 w-8 with rounded-full object-cover. Decorative — alt="" unless the array supplies one. With no src, the initials render in a token-colored circle instead. |
name |
string|null |
null |
Profile name text. Rendered at --text-wk-sm with body-weight font. |
as |
'div' | 'button' |
'div' |
The element to render. button gives you a real control: the browser supplies Enter and Space activation and the button role, with no JavaScript involved. Prefer it over interactive whenever the profile is itself the thing being clicked. Keep div when the profile sits inside something that is already a control — a button inside a button is invalid HTML. |
interactive |
bool |
false |
Turns a div profile into a focusable button-like element with role="button" + tabindex="0" plus Enter / Space keyboard handlers that synthesize a click. Use it when the surrounding markup rules out a real <button>; otherwise reach for as="button", which gets the same behavior from the browser instead of from Alpine. Adds a focus-visible ring matching the canonical button focus state. Has no effect on as="button", which is already a control. Default false preserves the presentational <div> byte-for-byte. |
scope |
string|null |
null |
Named personalization scope for block-level class overrides. |
When both avatar and name are omitted, the component renders an empty flex container — pass arbitrary children via the default slot for full control.
Interactive profile inside a dropdown trigger
The most common interactive use case — profile menu in a navbar. Without interactive, the <div> has no focusable child, so keyboard users can Tab past it without opening the menu.
The same menu with a real button
as="button" replaces the synthesized keyboard model with the browser's own. The
markup is one attribute different and the rendering is identical; what changes is that
the control is a button in the served HTML, before any JavaScript runs.
Accessibility
- The avatar
<img>is decorative:alt=""so the accessible name comes from thenametext or surrounding interactive element - By default
Profileis not interactive — it renders a plain<div>. Give itas="button"when the profile is the control, or wrap it in an<a>/<button>/ dropdown trigger when the surrounding element is as="button"is the accessible default for a clickable profile: Space and Enter both activate it, and it is a button to assistive tech from the first paint.interactivesynthesizes the same two keys from Alpine handlers, which means they do nothing until Alpine has booted — use it only where a real<button>is not possible, such as inside another control- When the profile is used inside an interactive wrapper (link/button/trigger), ensure the wrapper has a clear accessible name — either from the visible
nametext or via an explicitaria-labelon the wrapper
<x-wirekit::dropdown.trigger>
<x-wirekit::profile avatar="/me.jpg" name="Jane Doe" />
</x-wirekit::dropdown.trigger>
Keyboard Interaction
The default <div> is presentational and takes no focus, so there is nothing to
operate.
| Shape | Focus | Keys |
|---|---|---|
| default | not focusable | — |
as="button" |
in the tab order | Enter and Space activate it (from the browser) |
interactive |
in the tab order via tabindex="0" |
Enter and Space activate it (from Alpine, so only once Alpine has booted) |
Pitfalls
- Don't reach for
interactivewhenas="button"will do. Both make the profile clickable, but only the button is one before Alpine has loaded, and only the button is announced with its role in the served HTML. - Don't put a link or another button in the slot of an
as="button"profile. A control nested inside a control is invalid HTML and unreachable by keyboard in practice. Keep the defaultdivfor that layout and make the inner element the control. - Pass
avataras an array when you want an initials fallback. A bare string is treated as an image URL;['src' => …, 'initials' => …]lets the component fall back to initials when the image is absent.
Design Tokens
| Token | Used for |
|---|---|
--color-wk-text |
Name text color |
--gap-wk-sm |
Gap between avatar and name |
--text-wk-sm |
Name font size |
--font-wk-body-weight |
Name font weight |