DataInteractive
A wrapper that forwards data-hover, data-active, and
data-focus attributes to its child element for consistent interactive
state styling — without relying on CSS pseudo-classes.
Basic Usage
Wrap any element to get data-hover, data-active, and
data-focus attributes applied automatically. Style with CSS attribute selectors.
<BitBhDataInteractive As="button" class="interactive-btn">
Hover, focus, or press me
</BitBhDataInteractive>.interactive-btn {
display: inline-flex;
align-items: center;
padding: 0.625rem 1.25rem;
font-size: 0.875rem;
font-weight: 500;
border: 1px solid #e4e4e7;
border-radius: 0.5rem;
background: #fff;
color: #18181b;
cursor: pointer;
transition: all 150ms ease;
}
/* Style via data attributes instead of :hover/:active/:focus */
.interactive-btn[data-hover] {
background: #f0f9ff;
border-color: #7dd3fc;
color: #0369a1;
}
.interactive-btn[data-active] {
background: #0ea5e9;
border-color: #0ea5e9;
color: #fff;
transform: scale(0.97);
}
.interactive-btn[data-focus] {
outline: 2px solid #2563eb;
outline-offset: 2px;
}Link Wrapper
Use As="a" to wrap a link element. The data attributes let you style
hover/focus/active states consistently across all interactive elements.
<BitBhDataInteractive As="a" href="/dashboard" class="interactive-link">
Dashboard
</BitBhDataInteractive>
<BitBhDataInteractive As="a" href="/settings" class="interactive-link">
Settings
</BitBhDataInteractive>.interactive-link {
display: inline-block;
padding: 0.5rem 1rem;
font-size: 0.875rem;
color: #2563eb;
text-decoration: none;
border-radius: 0.375rem;
transition: all 150ms ease;
}
.interactive-link[data-hover] {
background: #eff6ff;
color: #1d4ed8;
}
.interactive-link[data-active] {
background: #dbeafe;
color: #1e40af;
}
.interactive-link[data-focus] {
outline: 2px solid #3b82f6;
outline-offset: 2px;
}Render Context
Access the interactive state via the render context to conditionally render content or apply classes based on hover, focus, and active states.
<BitBhDataInteractive As="button" class="interactive-card">
<Template Context="state">
<span class="card-icon">
@(state.Active ? "⚡" : state.Hover ? "👆" : "🖱️")
</span>
<span class="card-label">
@if (state.Active) { <span>Pressing!</span> }
else if (state.Hover) { <span>Hovering…</span> }
else if (state.Focus) { <span>Focused (Tab here)</span> }
else { <span>Interact with me</span> }
</span>
</Template>
</BitBhDataInteractive>.interactive-card {
display: inline-flex;
align-items: center;
gap: 0.75rem;
padding: 1rem 1.5rem;
border: 2px solid #e4e4e7;
border-radius: 0.75rem;
background: #fff;
cursor: pointer;
transition: all 200ms ease;
font-size: 1rem;
}
.interactive-card[data-hover] {
border-color: #a78bfa;
background: #faf5ff;
}
.interactive-card[data-active] {
border-color: #7c3aed;
background: #ede9fe;
transform: scale(0.96);
}
.interactive-card[data-focus] {
outline: 2px solid #7c3aed;
outline-offset: 2px;
}
.card-icon { font-size: 1.5rem; }Disabled State
When Disabled="true", all interactive state tracking is suppressed and
data-disabled is emitted instead.
<BitBhDataInteractive As="button" class="interactive-btn" Disabled="true">
I'm disabled
</BitBhDataInteractive>
<BitBhDataInteractive As="button" class="interactive-btn">
I'm enabled
</BitBhDataInteractive>.interactive-btn {
display: inline-flex;
align-items: center;
padding: 0.625rem 1.25rem;
font-size: 0.875rem;
font-weight: 500;
border: 1px solid #e4e4e7;
border-radius: 0.5rem;
background: #fff;
color: #18181b;
cursor: pointer;
transition: all 150ms ease;
}
/* Style via data attributes instead of :hover/:active/:focus */
.interactive-btn[data-hover] {
background: #f0f9ff;
border-color: #7dd3fc;
color: #0369a1;
}
.interactive-btn[data-active] {
background: #0ea5e9;
border-color: #0ea5e9;
color: #fff;
transform: scale(0.97);
}
.interactive-btn[data-focus] {
outline: 2px solid #2563eb;
outline-offset: 2px;
}
/* When disabled, interactive state tracking is suppressed and
data-disabled is emitted instead. */
.interactive-btn[data-disabled] {
opacity: 0.4;
cursor: not-allowed;
pointer-events: none;
}Card Grid
Build interactive cards that respond to hover, focus, and press. The data attributes give you full control over each state's visual treatment.
<div class="card-grid">
<BitBhDataInteractive As="div" class="card" tabindex="0">
<div class="card-title">Analytics</div>
<div class="card-desc">View your dashboard metrics</div>
</BitBhDataInteractive>
<BitBhDataInteractive As="div" class="card" tabindex="0">
<div class="card-title">Reports</div>
<div class="card-desc">Generate monthly reports</div>
</BitBhDataInteractive>
</div>.card-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
gap: 1rem;
}
.card {
padding: 1.25rem;
border: 1px solid #e4e4e7;
border-radius: 0.75rem;
background: #fff;
cursor: pointer;
transition: all 150ms ease;
}
.card[data-hover] {
border-color: #a78bfa;
box-shadow: 0 4px 12px rgba(124, 58, 237, 0.1);
transform: translateY(-2px);
}
.card[data-active] {
border-color: #7c3aed;
background: #faf5ff;
transform: translateY(0);
box-shadow: none;
}
.card[data-focus] {
outline: 2px solid #7c3aed;
outline-offset: 2px;
}
.card-title { font-weight: 600; margin-bottom: 0.25rem; }
.card-desc { font-size: 0.8rem; color: #71717a; }State Inspector
A live view of the data attributes being applied. Interact with the element and watch the state indicators update in real time.
<BitBhDataInteractive As="button" class="inspector-target">
<Template Context="s">
<span>Interact with this button</span>
<div class="inspector-states">
<span>data-hover: @(s.Hover ? "✓" : "✗")</span>
<span>data-focus: @(s.Focus ? "✓" : "✗")</span>
<span>data-active: @(s.Active ? "✓" : "✗")</span>
</div>
</Template>
</BitBhDataInteractive>.inspector-target {
display: flex;
flex-direction: column;
align-items: center;
gap: 0.75rem;
padding: 1.5rem 2rem;
border: 2px solid #e4e4e7;
border-radius: 0.75rem;
background: #fafafa;
cursor: pointer;
transition: all 150ms ease;
min-width: 280px;
}
.inspector-target[data-hover] { border-color: #60a5fa; background: #eff6ff; }
.inspector-target[data-active] { border-color: #2563eb; background: #dbeafe; }
.inspector-target[data-focus] { outline: 2px solid #2563eb; outline-offset: 2px; }
.inspector-label { font-weight: 500; }
.inspector-states {
display: flex;
gap: 0.5rem;
flex-wrap: wrap;
}
.state-badge {
display: inline-block;
padding: 0.25rem 0.5rem;
font-size: 0.75rem;
font-family: monospace;
border-radius: 0.25rem;
background: #f4f4f5;
color: #52525b;
border: 1px solid #e4e4e7;
}
.state-badge.state-on {
background: #dcfce7;
color: #166534;
border-color: #86efac;
}