Skip to content

Instantly share code, notes, and snippets.

@sunmeat
Created January 22, 2026 11:49
Show Gist options
  • Select an option

  • Save sunmeat/931ceaf79d81bf96bb53bb673f900cdb to your computer and use it in GitHub Desktop.

Select an option

Save sunmeat/931ceaf79d81bf96bb53bb673f900cdb to your computer and use it in GitHub Desktop.
приклад вбудованих компонентів + скоуп цсс
@* ContactInfo.razor — демонстрація всіх основних вбудованих Blazor input-компонентів *@
<div class="contacts-wrapper">
<div class="contacts-container">
<div class="header-section">
<h3 class="title">Контактна інформація</h3>
<div class="title-underline"></div>
</div>
<div class="contact-display">
<div class="contact-item email-item">
<span class="icon">✉️</span>
<span class="label">Основний email:</span>
<span class="value">@Email</span>
</div>
@if (ShowPhone)
{
<div class="contact-item phone-item fade-in">
<span class="icon">📞</span>
<span class="label">Телефон:</span>
<span class="value">@Phone</span>
</div>
}
</div>
<div class="form-section">
<!-- ─────────────────────────────────────────────── -->
<!-- InputCheckbox — аналог <input type="checkbox"> -->
<!-- Відмінність: автоматична інтеграція з EditForm + валідацією -->
<!-- Підтримує @bind-Value, aria-атрибути автоматично -->
<!-- ─────────────────────────────────────────────── -->
<div class="input-group checkbox-group">
<label class="checkbox-label">
<InputCheckbox @bind-Value="ShowPhone" />
<span class="checkbox-text">Показати номер телефону</span>
<span class="checkbox-custom"></span>
</label>
</div>
<!-- ─────────────────────────────────────────────── -->
<!-- InputText — аналог <input type="text"> -->
<!-- Відмінність: двосторонній біндинг через @bind-Value -->
<!-- Автоматично додає aria-invalid при помилках у формі -->
<!-- ─────────────────────────────────────────────── -->
<div class="input-group">
<label class="input-label">
<span class="label-text">Альтернативний email:</span>
<InputText @bind-Value="AlternativeEmail"
class="input-field"
placeholder="наприклад, robota@example.com" />
<small class="input-hint">
@if (string.IsNullOrEmpty(AlternativeEmail))
{
<span class="hint-empty">Поле не заповнено</span>
}
else
{
<span class="hint-filled">✓ Введено: @AlternativeEmail</span>
}
</small>
</label>
</div>
<!-- ─────────────────────────────────────────────── -->
<!-- InputTextArea — аналог <textarea> -->
<!-- Відмінність: той самий @bind-Value, підтримує рядки -->
<!-- ─────────────────────────────────────────────── -->
<div class="input-group">
<label class="input-label">
<span class="label-text">Коротка біо / примітка:</span>
<InputTextArea @bind-Value="Bio"
rows="4"
class="textarea-field"
placeholder="Напишіть пару слів про себе..." />
<small class="char-counter">@Bio.Length / 500 символів</small>
</label>
</div>
<div class="input-row-dual">
<!-- ─────────────────────────────────────────────── -->
<!-- InputNumber<TValue> — аналог <input type="number"> -->
<!-- Відмінність: типобезпечний (тут int), підтримує min/max/step -->
<!-- У .NET 9+ також підтримує type="range" (слайдер) -->
<!-- ─────────────────────────────────────────────── -->
<div class="input-group half-width">
<label class="input-label">
<span class="label-text">Роки досвіду в .NET:</span>
<div class="number-input-wrapper">
<InputNumber @bind-Value="YearsOfExperience"
class="input-field number-field"
min="0" max="50" step="1" />
<span class="number-badge">@YearsOfExperience років</span>
</div>
</label>
</div>
<!-- ─────────────────────────────────────────────── -->
<!-- InputDate<TValue> — аналог <input type="date"> -->
<!-- Відмінність: працює з DateTime/DateOnly, локалізація -->
<!-- ─────────────────────────────────────────────── -->
<div class="input-group half-width">
<label class="input-label">
<span class="label-text">Дата народження:</span>
<InputDate @bind-Value="BirthDate" class="input-field date-field" />
</label>
</div>
</div>
<!-- ─────────────────────────────────────────────── -->
<!-- InputSelect<TValue> — аналог <select> -->
<!-- Відмінність: @bind-Value працює з будь-яким типом -->
<!-- ─────────────────────────────────────────────── -->
<div class="input-group">
<label class="input-label">
<span class="label-text">Улюблена технологія:</span>
<div class="select-wrapper">
<InputSelect @bind-Value="FavoriteTech" class="input-field select-field">
<option value="">Оберіть технологію...</option>
<option value="Blazor">🔥 Blazor</option>
<option value="MAUI">📱 .NET MAUI</option>
<option value="AspNet">🌐 ASP.NET Core</option>
<option value="EfCore">💾 Entity Framework</option>
</InputSelect>
<span class="select-arrow">▼</span>
</div>
</label>
</div>
<!-- ─────────────────────────────────────────────── -->
<!-- InputRadioGroup + InputRadio — вибір з групи -->
<!-- Відмінність: групує радіо-кнопки, @bind-Value один на групу -->
<!-- Працює з рядками, enum тощо -->
<!-- ─────────────────────────────────────────────── -->
<div class="input-group radio-section">
<span class="label-text">Режим рендерингу:</span>
<InputRadioGroup @bind-Value="RenderMode" class="radio-group">
<label class="radio-label">
<InputRadio Value="@("Server")" />
<span class="radio-custom"></span>
<span class="radio-text">
<strong>Server</strong>
<small>Рендеринг на сервері</small>
</span>
</label>
<label class="radio-label">
<InputRadio Value="@("WebAssembly")" />
<span class="radio-custom"></span>
<span class="radio-text">
<strong>WebAssembly</strong>
<small>Виконання в браузері</small>
</span>
</label>
<label class="radio-label">
<InputRadio Value="@("Auto")" />
<span class="radio-custom"></span>
<span class="radio-text">
<strong>Auto</strong>
<small>Автоматичний вибір</small>
</span>
</label>
</InputRadioGroup>
<div class="radio-indicator">
<span class="indicator-label">Обрано:</span>
<span class="indicator-value">@RenderMode</span>
</div>
</div>
</div>
</div>
</div>
@code {
[Parameter] public string Email { get; set; } = "sunmeatrich@gmail.com";
[Parameter] public string Phone { get; set; } = "+380 (63) 03-000-35";
private bool ShowPhone { get; set; } = false;
private string AlternativeEmail { get; set; } = "";
private string Bio { get; set; } = "";
private int YearsOfExperience { get; set; } = 5;
private DateTime? BirthDate { get; set; } = new DateTime(1989, 3, 10);
private string FavoriteTech { get; set; } = "Blazor";
private string RenderMode { get; set; } = "Auto";
}
<style>
<!-- ───────────────────────────────────────────────
// CSS ІЗОЛЯЦІЯ в Blazor (.razor.css або <style>)
// ───────────────────────────────────────────────
// 1. Стилі автоматично скоуплені тільки до цього компонента
// 2. Blazor додає унікальний атрибут типу b-abc123 до елементів
// 3. Стилі НЕ протікають на інші компоненти → немає конфліктів
// 4. Можна використовувати :global(...) якщо треба глобальний стиль
// 5. У .NET 8+ рекомендовано виносити в окремий файл *.razor.css
───────────────────────────────────────────────── -->
.contacts-wrapper {
width: 100%;
min-height: 100vh;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
padding: 2rem 1rem;
display: flex;
justify-content: center;
align-items: flex-start;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
}
.contacts-container {
max-width: 800px;
width: 100%;
background: #ffffff;
border-radius: 24px;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3),
0 0 0 1px rgba(255, 255, 255, 0.1);
padding: 3rem;
animation: slideIn 0.6s ease-out;
}
@@keyframes slideIn {
from {
opacity: 0;
transform: translateY(30px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.header-section {
text-align: center;
margin-bottom: 2.5rem;
}
.title {
font-size: 2.5rem;
font-weight: 700;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
margin: 0;
padding: 0;
letter-spacing: -0.5px;
}
.title-underline {
width: 80px;
height: 4px;
background: linear-gradient(90deg, #667eea 0%, #764ba2 100%);
margin: 1rem auto 0;
border-radius: 2px;
animation: expandWidth 0.8s ease-out;
}
@@keyframes expandWidth {
from {
width: 0;
}
to {
width: 80px;
}
}
.contact-display {
background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
border-radius: 16px;
padding: 1.5rem;
margin-bottom: 2rem;
display: flex;
flex-direction: column;
gap: 1rem;
}
.contact-item {
display: flex;
align-items: center;
gap: 0.75rem;
padding: 0.75rem;
background: white;
border-radius: 12px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
transition: transform 0.3s ease, box-shadow 0.3s ease;
}
.contact-item:hover {
transform: translateY(-2px);
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.12);
}
.contact-item .icon {
font-size: 1.5rem;
flex-shrink: 0;
}
.contact-item .label {
font-weight: 600;
color: #4a5568;
flex-shrink: 0;
}
.contact-item .value {
color: #2d3748;
font-weight: 500;
margin-left: auto;
}
.fade-in {
animation: fadeIn 0.4s ease-out;
}
@@keyframes fadeIn {
from {
opacity: 0;
transform: translateX(-10px);
}
to {
opacity: 1;
transform: translateX(0);
}
}
.form-section {
display: flex;
flex-direction: column;
gap: 1.5rem;
}
.input-group {
display: flex;
flex-direction: column;
gap: 0.5rem;
position: relative;
}
.input-label {
display: flex;
flex-direction: column;
gap: 0.5rem;
width: 100%;
}
.label-text {
font-weight: 600;
color: #2d3748;
font-size: 0.95rem;
display: flex;
align-items: center;
gap: 0.5rem;
}
.label-text::before {
content: '';
width: 4px;
height: 18px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border-radius: 2px;
}
.input-field {
padding: 0.875rem 1rem;
border: 2px solid #e2e8f0;
border-radius: 12px;
font-size: 1rem;
color: #2d3748;
background: white;
transition: all 0.3s ease;
font-family: inherit;
outline: none;
}
.input-field:focus {
border-color: #667eea;
box-shadow: 0 0 0 4px rgba(102, 126, 234, 0.1);
transform: translateY(-1px);
}
.input-field:hover:not(:focus) {
border-color: #cbd5e0;
}
.input-field::placeholder {
color: #a0aec0;
}
.checkbox-group {
background: linear-gradient(135deg, #ffeaa7 0%, #fdcb6e 100%);
padding: 1.25rem;
border-radius: 12px;
margin-bottom: 0.5rem;
}
.checkbox-label {
display: flex;
align-items: center;
gap: 0.75rem;
cursor: pointer;
position: relative;
user-select: none;
}
.checkbox-label input[type="checkbox"] {
position: absolute;
opacity: 0;
cursor: pointer;
width: 0;
height: 0;
}
.checkbox-custom {
width: 24px;
height: 24px;
border: 2px solid #2d3748;
border-radius: 6px;
background: white;
position: relative;
transition: all 0.3s ease;
flex-shrink: 0;
}
.checkbox-label:hover .checkbox-custom {
border-color: #667eea;
transform: scale(1.1);
}
.checkbox-label input[type="checkbox"]:checked ~ .checkbox-custom {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border-color: #667eea;
}
.checkbox-label input[type="checkbox"]:checked ~ .checkbox-custom::after {
content: '✓';
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
color: white;
font-weight: bold;
font-size: 14px;
}
.checkbox-text {
font-weight: 600;
color: #2d3748;
font-size: 1.05rem;
}
.textarea-field {
padding: 0.875rem 1rem;
border: 2px solid #e2e8f0;
border-radius: 12px;
font-size: 1rem;
color: #2d3748;
background: white;
transition: all 0.3s ease;
font-family: inherit;
resize: vertical;
min-height: 100px;
outline: none;
}
.textarea-field:focus {
border-color: #667eea;
box-shadow: 0 0 0 4px rgba(102, 126, 234, 0.1);
}
.char-counter {
text-align: right;
color: #718096;
font-size: 0.85rem;
font-style: italic;
}
.input-hint {
font-size: 0.875rem;
color: #718096;
padding-left: 0.5rem;
}
.hint-empty {
color: #a0aec0;
}
.hint-filled {
color: #48bb78;
font-weight: 500;
}
.input-row-dual {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 1.5rem;
}
.half-width {
width: 100%;
}
@@media (max-width: 640px) {
.input-row-dual {
grid-template-columns: 1fr;
}
}
.number-input-wrapper {
display: flex;
align-items: center;
gap: 0.75rem;
}
.number-field {
flex: 1;
min-width: 0;
}
.number-badge {
padding: 0.5rem 1rem;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
border-radius: 20px;
font-weight: 600;
font-size: 0.9rem;
white-space: nowrap;
box-shadow: 0 2px 8px rgba(102, 126, 234, 0.3);
}
.date-field {
cursor: pointer;
}
.date-field::-webkit-calendar-picker-indicator {
cursor: pointer;
filter: invert(0.5);
transition: filter 0.3s ease;
}
.date-field:hover::-webkit-calendar-picker-indicator {
filter: invert(0.3);
}
.select-wrapper {
position: relative;
}
.select-field {
appearance: none;
-webkit-appearance: none;
-moz-appearance: none;
width: 100%;
cursor: pointer;
padding-right: 3rem;
}
.select-arrow {
position: absolute;
right: 1rem;
top: 50%;
transform: translateY(-50%);
pointer-events: none;
color: #667eea;
font-size: 0.8rem;
transition: transform 0.3s ease;
}
.select-field:focus ~ .select-arrow {
transform: translateY(-50%) rotate(180deg);
}
.radio-section {
background: linear-gradient(135deg, #a8edea 0%, #fed6e3 100%);
padding: 1.5rem;
border-radius: 16px;
}
.radio-section > .label-text {
margin-bottom: 1rem;
font-size: 1.05rem;
}
.radio-group {
display: flex;
flex-direction: column;
gap: 1rem;
margin-bottom: 1rem;
}
.radio-label {
display: flex;
align-items: center;
gap: 1rem;
padding: 1rem;
background: white;
border: 2px solid transparent;
border-radius: 12px;
cursor: pointer;
transition: all 0.3s ease;
position: relative;
}
.radio-label:hover {
border-color: #667eea;
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.15);
transform: translateX(4px);
}
.radio-label input[type="radio"] {
position: absolute;
opacity: 0;
cursor: pointer;
width: 0;
height: 0;
}
.radio-custom {
width: 24px;
height: 24px;
border: 2px solid #cbd5e0;
border-radius: 50%;
position: relative;
flex-shrink: 0;
transition: all 0.3s ease;
background: white;
}
.radio-label input[type="radio"]:checked ~ .radio-custom {
border-color: #667eea;
border-width: 3px;
}
.radio-label input[type="radio"]:checked ~ .radio-custom::after {
content: '';
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 10px;
height: 10px;
border-radius: 50%;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
animation: scaleIn 0.2s ease-out;
}
@@keyframes scaleIn {
from {
transform: translate(-50%, -50%) scale(0);
}
to {
transform: translate(-50%, -50%) scale(1);
}
}
.radio-text {
display: flex;
flex-direction: column;
gap: 0.25rem;
}
.radio-text strong {
color: #2d3748;
font-size: 1rem;
}
.radio-text small {
color: #718096;
font-size: 0.85rem;
}
.radio-indicator {
display: flex;
align-items: center;
gap: 0.75rem;
padding: 0.75rem 1rem;
background: white;
border-radius: 8px;
border-left: 4px solid #667eea;
}
.indicator-label {
font-weight: 600;
color: #4a5568;
font-size: 0.9rem;
}
.indicator-value {
padding: 0.25rem 0.75rem;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
border-radius: 12px;
font-weight: 600;
font-size: 0.85rem;
}
@@media (max-width: 768px) {
.contacts-container {
padding: 2rem 1.5rem;
border-radius: 16px;
}
.title {
font-size: 2rem;
}
.contact-item {
flex-direction: column;
align-items: flex-start;
gap: 0.5rem;
}
.contact-item .value {
margin-left: 0;
}
.radio-label {
padding: 0.875rem;
}
.number-input-wrapper {
flex-direction: column;
align-items: stretch;
}
.number-badge {
text-align: center;
}
}
@@media (max-width: 480px) {
.contacts-wrapper {
padding: 1rem 0.5rem;
}
.contacts-container {
padding: 1.5rem 1rem;
}
.title {
font-size: 1.75rem;
}
.input-field,
.textarea-field {
font-size: 0.95rem;
}
}
.checkbox-label:focus-within .checkbox-custom,
.radio-label:focus-within .radio-custom {
outline: 2px solid #667eea;
outline-offset: 2px;
}
* {
box-sizing: border-box;
}
@@media (prefers-reduced-motion: reduce) {
*,
*::before,
*::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
}
}
</style>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment