Single form showcasing Vertical, Horizontal, Tabbed, and List layouts Async
<!-- Material Design 3 Self-Contained Form -->
<style>
/* Material Design 3 Self-Contained Styles - Using !important to override any conflicting styles */
.md-form-container {
font-family: 'Roboto', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif !important;
max-width: 100% !important;
margin: 0 !important;
padding: 20px !important;
line-height: 1.5 !important;
color: #1c1b1f !important;
background: #fef7ff !important;
border: none !important;
box-sizing: border-box !important;
position: relative !important;
}
.md-form {
width: 100% !important;
background: #ffffff !important;
border-radius: 28px !important;
padding: 32px !important;
box-shadow: 0 1px 2px rgba(0,0,0,0.3), 0 2px 6px 2px rgba(0,0,0,0.15) !important;
border: none !important;
margin: 0 !important;
box-sizing: border-box !important;
font-family: 'Roboto', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif !important;
}
/* Reset any Bootstrap interference */
.md-form * {
box-sizing: border-box !important;
}
/* Material Design Form Fields */
.md-field {
margin-bottom: 32px !important;
position: relative !important;
width: 100% !important;
font-family: 'Roboto', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif !important;
}
/* Model list container styling to blend with Material Design */
.md-model-list-container {
background: transparent !important;
border: none !important;
padding: 0 !important;
margin: 0 !important;
font-family: 'Roboto', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif !important;
}
/* Override Bootstrap styles for model list items within Material Design */
.md-model-list-container .card {
border: 1px solid #79747e !important;
border-radius: 12px !important;
box-shadow: none !important;
margin-bottom: 16px !important;
background: #ffffff !important;
}
.md-model-list-container .card-header {
background: #f7f2fa !important;
border-bottom: 1px solid #e7e0ec !important;
border-radius: 12px 12px 0 0 !important;
color: #1c1b1f !important;
font-family: 'Roboto', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif !important;
font-weight: 500 !important;
}
.md-model-list-container .btn {
border-radius: 20px !important;
font-family: 'Roboto', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif !important;
font-weight: 500 !important;
text-transform: none !important;
}
.md-model-list-container .btn-primary {
background: #6750a4 !important;
border-color: #6750a4 !important;
}
.md-model-list-container .btn-danger {
background: #ba1a1a !important;
border-color: #ba1a1a !important;
}
.md-model-list-wrapper {
background: transparent !important;
margin-bottom: 32px !important;
}
.md-model-list-container {
border: 1px solid #e7e0ec !important;
border-radius: 24px !important;
padding: 16px 20px !important;
background: #fff !important;
box-shadow: 0 1px 3px rgba(0,0,0,0.08) !important;
}
.md-model-list-items {
display: flex !important;
flex-direction: column !important;
gap: 16px !important;
}
.md-model-card {
border: 1px solid #e7e0ec !important;
border-radius: 20px !important;
background: #ffffff !important;
padding: 16px 20px !important;
box-shadow: 0 1px 3px rgba(0,0,0,0.08) !important;
}
.md-model-card__header {
display: flex !important;
justify-content: space-between !important;
align-items: center !important;
margin-bottom: 12px !important;
}
.md-model-card__body {
padding: 0 !important;
}
.md-model-list-actions {
margin-top: 12px !important;
display: flex !important;
justify-content: flex-end !important;
}
.md-button-tonal {
background: #e8def8 !important;
color: #1c1b1f !important;
}
.md-button-tonal:hover {
background: #cdc2db !important;
}
.md-button__icon {
margin-right: 8px !important;
width: 20px !important;
height: 20px !important;
display: inline-flex !important;
align-items: center !important;
justify-content: center !important;
flex: 0 0 20px !important;
fill: currentColor !important;
}
.md-button__label {
font-weight: 500 !important;
}
/* Layout card styling */
.md-layout-card {
background: #ffffff !important;
border-radius: 24px !important;
padding: 24px 28px !important;
margin-bottom: 32px !important;
box-shadow: 0 1px 3px rgba(0,0,0,0.2), 0 4px 8px rgba(0,0,0,0.1) !important;
border: 1px solid #e7e0ec !important;
}
.md-layout-card__header {
margin-bottom: 12px !important;
}
.md-layout-card__title {
font-size: 18px !important;
font-weight: 600 !important;
color: #1c1b1f !important;
}
.md-layout-card__help {
color: #49454f !important;
font-size: 14px !important;
margin-bottom: 12px !important;
}
.md-layout-card__content {
display: flex !important;
flex-direction: column !important;
gap: 16px !important;
}
.md-field-label {
display: block !important;
color: #49454f !important;
font-size: 14px !important;
font-weight: 500 !important;
margin-bottom: 8px !important;
position: relative !important;
font-family: 'Roboto', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif !important;
line-height: 1.4 !important;
}
.md-field-label.required::after {
content: ' *' !important;
color: #ba1a1a !important;
}
/* Material Design Outlined Text Fields */
.md-text-field {
position: relative;
width: 100%;
}
/* Field container */
.md-field {
margin: 16px 0;
}
/* Field with icon layout */
.md-field-with-icon {
display: flex !important;
align-items: flex-start !important;
gap: 12px !important;
font-family: 'Roboto', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif !important;
}
/* Input wrapper for proper label positioning */
.md-input-wrapper {
position: relative !important;
flex: 1 !important;
width: 100% !important;
}
.md-input {
width: 100% !important;
padding: 16px !important;
border: 1px solid #79747e !important;
border-radius: 4px !important;
background: transparent !important;
color: #1c1b1f !important;
font-size: 16px !important;
font-family: 'Roboto', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif !important;
outline: none !important;
transition: all 0.15s cubic-bezier(0.4, 0, 0.2, 1) !important;
box-sizing: border-box !important;
line-height: 1.5 !important;
margin: 0 !important;
}
.md-input:focus {
border-color: #6750a4 !important;
border-width: 2px !important;
padding: 15px !important; /* Adjust for thicker border */
box-shadow: none !important;
}
/* Icon styling - positioned outside to the left of input */
.md-icon {
display: flex !important;
align-items: center !important;
justify-content: center !important;
width: 24px !important;
height: 24px !important;
margin-top: 16px !important; /* Align with input padding */
color: #49454f !important;
flex-shrink: 0 !important;
transition: color 0.15s cubic-bezier(0.4, 0, 0.2, 1) !important;
fill: currentColor !important;
}
.md-field-with-icon:focus-within .md-icon {
color: #6750a4 !important;
}
.md-input:focus + .md-floating-label,
.md-input:not(:placeholder-shown) + .md-floating-label,
.md-textarea:focus + .md-floating-label,
.md-textarea:not(:placeholder-shown) + .md-floating-label,
.md-select:focus + .md-floating-label {
transform: translateY(-28px) scale(0.75) !important;
color: #6750a4 !important;
background: #ffffff !important;
padding: 0 4px !important;
}
.md-floating-label {
position: absolute !important;
left: 16px !important;
top: 16px !important;
color: #49454f !important;
font-size: 16px !important;
transition: all 0.15s cubic-bezier(0.4, 0, 0.2, 1) !important;
pointer-events: none !important;
background: transparent !important;
z-index: 1 !important;
transform-origin: left top !important;
font-family: 'Roboto', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif !important;
font-weight: 400 !important;
line-height: 1.4 !important;
}
.md-input:focus + .md-floating-label,
.md-input:not(:placeholder-shown) + .md-floating-label {
transform: translateY(-28px) scale(0.75) !important;
color: #6750a4;
background: #ffffff;
padding: 0 4px;
}
.md-input.error {
border-color: #ba1a1a;
}
.md-input.error:focus {
border-color: #ba1a1a;
}
.md-input.error + .md-floating-label {
color: #ba1a1a;
}
/* Material Design Select */
.md-select {
width: 100%;
padding: 16px;
border: 1px solid #79747e;
border-radius: 4px;
background: transparent;
color: #1c1b1f;
font-size: 16px;
font-family: inherit;
outline: none;
cursor: pointer;
transition: all 0.15s cubic-bezier(0.4, 0, 0.2, 1);
box-sizing: border-box;
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3e%3cpath stroke='%2349454f' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='m6 8 4 4 4-4'/%3e%3c/svg%3e");
background-position: right 12px center;
background-repeat: no-repeat;
background-size: 16px;
padding-right: 40px;
}
.md-select:focus {
border-color: #6750a4;
border-width: 2px;
padding: 15px 39px 15px 15px; /* Adjust for thicker border */
}
.md-select:focus + .md-floating-label {
transform: translateY(-28px) scale(0.75);
color: #6750a4;
background: #ffffff;
padding: 0 4px;
}
/* Material Design Textarea */
.md-textarea {
width: 100%;
min-height: 120px;
padding: 16px;
border: 1px solid #79747e;
border-radius: 4px;
background: transparent;
color: #1c1b1f;
font-size: 16px;
font-family: inherit;
outline: none;
resize: vertical;
transition: all 0.15s cubic-bezier(0.4, 0, 0.2, 1);
box-sizing: border-box;
}
.md-textarea:focus {
border-color: #6750a4;
border-width: 2px;
padding: 15px; /* Adjust for thicker border */
}
.md-textarea:focus + .md-floating-label,
.md-textarea:not(:placeholder-shown) + .md-floating-label {
transform: translateY(-28px) scale(0.75);
color: #6750a4;
background: #ffffff;
padding: 0 4px;
}
/* Material Design Checkboxes */
.md-checkbox-container {
display: flex;
align-items: flex-start;
gap: 16px;
margin: 16px 0;
cursor: pointer;
}
.md-checkbox {
width: 18px;
height: 18px;
border: 2px solid #79747e;
border-radius: 2px;
background: transparent;
cursor: pointer;
position: relative;
transition: all 0.15s cubic-bezier(0.4, 0, 0.2, 1);
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
margin-top: 2px; /* Align with text baseline */
flex-shrink: 0;
}
.md-checkbox:checked {
background: #6750a4;
border-color: #6750a4;
}
.md-checkbox:checked::after {
content: '';
position: absolute;
top: 1px;
left: 4px;
width: 6px;
height: 10px;
border: solid white;
border-width: 0 2px 2px 0;
transform: rotate(45deg);
}
.md-checkbox-label {
color: #1c1b1f;
font-size: 16px;
cursor: pointer;
line-height: 1.5;
}
/* Material Design Buttons */
.md-button {
display: inline-flex;
align-items: center;
justify-content: center;
padding: 10px 24px;
border: none;
border-radius: 20px;
font-size: 14px;
font-weight: 500;
font-family: inherit;
cursor: pointer;
transition: all 0.15s cubic-bezier(0.4, 0, 0.2, 1);
text-decoration: none;
box-sizing: border-box;
min-width: 64px;
height: 40px;
position: relative;
overflow: hidden;
}
.md-button-filled {
background: #6750a4;
color: #ffffff;
box-shadow: 0 1px 2px rgba(0,0,0,0.3), 0 1px 3px 1px rgba(0,0,0,0.15);
}
.md-button-filled:hover {
background: #5a43a0;
box-shadow: 0 1px 2px rgba(0,0,0,0.3), 0 2px 6px 2px rgba(0,0,0,0.15);
transform: translateY(-1px);
}
.md-button-filled:active {
transform: translateY(0);
box-shadow: 0 1px 2px rgba(0,0,0,0.3), 0 1px 3px 1px rgba(0,0,0,0.15);
}
/* Help Text */
.md-help-text {
font-size: 12px;
color: #49454f;
margin-top: 4px;
line-height: 1.33;
padding-left: 16px;
}
/* Error Text */
.md-error-text {
font-size: 12px;
color: #ba1a1a;
margin-top: 4px;
line-height: 1.33;
font-weight: 400;
padding-left: 16px;
}
/* Number and Date Inputs */
.md-input[type="number"],
.md-input[type="date"],
.md-input[type="email"],
.md-input[type="password"],
.md-input[type="tel"],
.md-input[type="url"] {
/* Inherit all md-input styles */
}
.md-input[type="color"] {
height: 56px;
padding: 8px;
cursor: pointer;
}
.md-input[type="range"] {
padding: 16px 8px;
}
/* Placeholder styling */
.md-input::placeholder {
color: transparent;
}
.md-input:focus::placeholder {
color: #49454f;
}
/* State layers for interactive elements */
.md-button-filled::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: currentColor;
opacity: 0;
transition: opacity 0.15s cubic-bezier(0.4, 0, 0.2, 1);
border-radius: inherit;
}
.md-button-filled:hover::before {
opacity: 0.08;
}
.md-button-filled:focus::before {
opacity: 0.12;
}
.md-button-filled:active::before {
opacity: 0.16;
}
/* Responsive Design */
@media (max-width: 768px) {
.md-form {
padding: 24px 16px;
border-radius: 28px;
}
.md-field {
margin-bottom: 24px;
}
}
/* Typography Scale */
.md-headline-small {
font-size: 24px;
font-weight: 400;
line-height: 32px;
color: #1c1b1f;
}
.md-body-large {
font-size: 16px;
font-weight: 400;
line-height: 24px;
color: #1c1b1f;
}
.md-body-medium {
font-size: 14px;
font-weight: 400;
line-height: 20px;
color: #49454f;
}
.md-label-large {
font-size: 14px;
font-weight: 500;
line-height: 20px;
color: #1c1b1f;
}
/* Surface colors and elevation */
.md-surface {
background: #fef7ff;
color: #1c1b1f;
}
.md-surface-container {
background: #f3f0ff;
color: #1c1b1f;
}
.md-surface-container-high {
background: #e7e0ec;
color: #1c1b1f;
}
</style>
<div class="md-form-container">
<form id=""
class="pydantic-form md-form col s12"
style=""
method="POST"
action="/layouts?style=material"
novalidate>
<div class="tab-layout md-tab-layout layout-tabbed-section" style="">
<div class="tab-navigation md-tab-navigation" role="tablist">
<button class="tab-button md-tab-button active" type="button" role="tab"
aria-selected="true" aria-controls="tab-0" onclick="switchTab('tab-0', this)">
Personal Info
</button>
<button class="tab-button md-tab-button" type="button" role="tab"
aria-selected="false" aria-controls="tab-1" onclick="switchTab('tab-1', this)">
Contact Info
</button>
<button class="tab-button md-tab-button" type="button" role="tab"
aria-selected="false" aria-controls="tab-2" onclick="switchTab('tab-2', this)">
Preferences
</button>
<button class="tab-button md-tab-button" type="button" role="tab"
aria-selected="false" aria-controls="tab-3" onclick="switchTab('tab-3', this)">
Task List
</button>
</div>
<div class="tab-content md-tab-content">
<div id="tab-0"
class="tab-panel active"
role="tabpanel"
style="display: block;"
aria-hidden="false">
<section class="md-layout-card">
<header class="md-layout-card__header">
<span class="md-layout-card__title">Personal Info</span>
</header>
<div class="md-layout-card__body">
<p class="md-layout-card__help">Vertical layout demonstration</p>
<div class="md-layout-card__content">
<div class="md-field">
<div class="md-field-with-icon">
<svg class="material-icons md-icon" viewBox="0 0 24 24" focusable="false" aria-hidden="true"><path d="M12 12c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm0 2c-2.67 0-8 1.34-8 4v2h16v-2c0-2.66-5.33-4-8-4z" /></svg>
<div class="md-input-wrapper">
<input type="text"
name="first_name"
id="first_name"
class="md-input"
value="Alex"
placeholder=" " >
<label class="md-floating-label" for="first_name">First Name *</label>
</div>
</div>
<div class="md-help-text">Your given name</div>
</div>
<div class="md-field">
<div class="md-field-with-icon">
<svg class="material-icons md-icon" viewBox="0 0 24 24" focusable="false" aria-hidden="true"><path d="M12 12c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm0 2c-2.67 0-8 1.34-8 4v2h16v-2c0-2.66-5.33-4-8-4z" /></svg>
<div class="md-input-wrapper">
<input type="text"
name="last_name"
id="last_name"
class="md-input"
value="Johnson"
placeholder=" " >
<label class="md-floating-label" for="last_name">Last Name *</label>
</div>
</div>
<div class="md-help-text">Your family name</div>
</div>
<div class="md-field">
<div class="md-field-with-icon">
<svg class="material-icons md-icon" viewBox="0 0 24 24" focusable="false" aria-hidden="true"><path d="M20 4H4c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V6c0-1.1-.9-2-2-2zm0 4-8 5-8-5V6l8 5 8-5v2z" /></svg>
<div class="md-input-wrapper">
<input type="email"
name="email"
id="email"
class="md-input"
value="alex.johnson@example.com"
placeholder=" " >
<label class="md-floating-label" for="email">Email Address *</label>
</div>
</div>
<div class="md-help-text">Your email address</div>
</div>
<div class="md-field">
<div class="md-field-with-icon">
<span class="material-icons md-icon">event</span>
<div class="md-input-wrapper">
<input type="date"
name="birth_date"
id="birth_date"
class="md-input"
value="1990-05-15"
placeholder=" " >
<label class="md-floating-label" for="birth_date">Date of Birth</label>
</div>
</div>
<div class="md-help-text">Your birth date (optional)</div>
</div>
</div>
</div>
</section>
</div>
<div id="tab-1"
class="tab-panel"
role="tabpanel"
style="display: none;"
aria-hidden="true">
<section class="md-layout-card">
<header class="md-layout-card__header">
<span class="md-layout-card__title">Contact Info</span>
</header>
<div class="md-layout-card__body">
<p class="md-layout-card__help">Horizontal layout demonstration</p>
<div class="md-layout-card__content">
<div class="md-field">
<div class="md-field-with-icon">
<span class="material-icons md-icon">phone</span>
<div class="md-input-wrapper">
<input type="tel"
name="phone"
id="phone"
class="md-input"
value="+1 (555) 987-6543"
placeholder=" " >
<label class="md-floating-label" for="phone">Phone Number</label>
</div>
</div>
<div class="md-help-text">Your contact phone number</div>
</div>
<div class="md-field">
<div class="md-field-with-icon">
<span class="material-icons md-icon">home</span>
<div class="md-input-wrapper">
<input type="text"
name="address"
id="address"
class="md-input"
value="456 Demo Street"
placeholder=" " >
<label class="md-floating-label" for="address">Street Address *</label>
</div>
</div>
<div class="md-help-text">Your street address</div>
</div>
<div class="md-field">
<div class="md-field-with-icon">
<span class="material-icons md-icon">building</span>
<div class="md-input-wrapper">
<input type="text"
name="city"
id="city"
class="md-input"
value="San Francisco"
placeholder=" " >
<label class="md-floating-label" for="city">City *</label>
</div>
</div>
<div class="md-help-text">City where you live</div>
</div>
<div class="md-field">
<div class="md-field-with-icon">
<span class="material-icons md-icon">mail</span>
<div class="md-input-wrapper">
<input type="text"
name="postal_code"
id="postal_code"
class="md-input"
value="94102"
placeholder=" " >
<label class="md-floating-label" for="postal_code">Postal Code</label>
</div>
</div>
<div class="md-help-text">ZIP or postal code</div>
</div>
</div>
</div>
</section>
</div>
<div id="tab-2"
class="tab-panel"
role="tabpanel"
style="display: none;"
aria-hidden="true">
<section class="md-layout-card">
<header class="md-layout-card__header">
<span class="md-layout-card__title">Preferences</span>
</header>
<div class="md-layout-card__body">
<p class="md-layout-card__help">Tabbed layout demonstration</p>
<div class="md-layout-card__content">
<div class="md-field">
<div class="md-checkbox-container">
<input type="checkbox"
name="notification_email"
id="notification_email"
class="md-checkbox"
value="true"
checked="checked">
<label for="notification_email" class="md-checkbox-label">Email Notifications</label>
</div>
<div class="md-help-text">Receive notifications via email</div>
</div>
<div class="md-field">
<div class="md-checkbox-container">
<input type="checkbox"
name="notification_sms"
id="notification_sms"
class="md-checkbox"
value="true"
>
<label for="notification_sms" class="md-checkbox-label">SMS Notifications</label>
</div>
<div class="md-help-text">Receive notifications via SMS</div>
</div>
<div class="md-field">
<div class="md-field-with-icon">
<span class="material-icons md-icon">palette</span>
<div class="md-input-wrapper">
<select name="theme"
id="theme"
class="md-select">
<option value=""></option>
<option value="light">βοΈ Light Theme</option>
<option value="dark" selected="selected">π Dark Theme</option>
<option value="auto">π Auto (System)</option>
</select>
<label class="md-floating-label" for="theme">UI Theme</label>
</div>
</div>
<div class="md-help-text">Choose your preferred theme</div>
</div>
<div class="md-field">
<div class="md-field-with-icon">
<span class="material-icons md-icon">public</span>
<div class="md-input-wrapper">
<select name="language"
id="language"
class="md-select">
<option value=""></option>
<option value="en" selected="selected">πΊπΈ English</option>
<option value="es">πͺπΈ Spanish</option>
<option value="fr">π«π· French</option>
<option value="de">π©πͺ German</option>
</select>
<label class="md-floating-label" for="language">Language</label>
</div>
</div>
<div class="md-help-text">Select your preferred language</div>
</div>
</div>
</div>
</section>
</div>
<div id="tab-3"
class="tab-panel"
role="tabpanel"
style="display: none;"
aria-hidden="true">
<section class="md-layout-card">
<header class="md-layout-card__header">
<span class="md-layout-card__title">Task List</span>
</header>
<div class="md-layout-card__body">
<p class="md-layout-card__help">List layout demonstration</p>
<div class="md-layout-card__content">
<div class="md-field">
<div class="md-field-with-icon">
<span class="material-icons md-icon">folder</span>
<div class="md-input-wrapper">
<input type="text"
name="project_name"
id="project_name"
class="md-input"
value="Website Redesign Project"
placeholder=" " >
<label class="md-floating-label" for="project_name">Project Name *</label>
</div>
</div>
<div class="md-help-text">Name of the project or task collection</div>
</div>
<div class="md-field">
<div class="md-model-list-container">
<section class="md-model-list-wrapper">
<label class="md-field-label required">Task List</label>
<div class="model-list-container md-model-list-container" data-field-name="tasks" data-min-items="1" data-max-items="10">
<div class="model-list-items md-model-list-items" id="tasks-items">
<div class="model-list-item card border mb-3"
data-index="0"
data-title-template="Item #{index}"
data-field-name="tasks">
<div class="card-header d-flex justify-content-between align-items-center">
<h6 class="mb-0">
<button class="btn btn-link text-decoration-none p-0 text-start"
type="button"
data-bs-toggle="collapse"
data-bs-target="#tasks_item_0_content"
aria-expanded="true"
aria-controls="tasks_item_0_content">
<i class="bi bi-chevron-down me-2"></i>
<i class="bi bi-card-list me-2"></i>
Item #1
</button>
</h6>
<button type="button"
class="btn btn-outline-danger btn-sm remove-item-btn"
data-index="0"
data-field-name="tasks"
title="Remove this item">
<i class="bi bi-trash"></i>
</button>
</div>
<div class="collapse show" id="tasks_item_0_content">
<div class="card-body"><div class="row">
<div class="col-md-6">
<div class="input-field col s12"><label for="tasks[0].task_name"><i class="material-icons">check_box</i> Task Description</label>
<input name="tasks[0].task_name" id="tasks[0].task_name" class="validate" value="Complete project setup and requirements gathering" minlength="1" maxlength="11" placeholder="Enter task description..." pattern="\d{3}-\d{2}-\d{4}" inputmode="numeric" autocomplete="off" type="text" />
<div class="help-text">What needs to be done?</div></div>
</div>
<div class="col-md-6">
<div class="input-field col s12"><div class="md-field">
<div class="md-field-with-icon">
<span class="md-icon material-icons">warning</span>
<div class="md-input-wrapper">
<select name="tasks[0].priority" id="tasks[0].priority" class="browser-default"><option value="low">π’ Low</option>
<option value="medium">π‘ Medium</option>
<option value="high" selected>π High</option>
<option value="urgent">π΄ Urgent</option></select>
<label class="md-floating-label" for="tasks[0].priority">Priority</label>
</div>
</div>
<div class="md-help-text">How important is this task?</div>
</div></div>
</div>
<div class="col-md-6">
<div class="input-field col s12"><label for="tasks[0].due_date"><i class="material-icons">event</i> Due Date</label>
<div class="birthdate-input-group"><input name="tasks[0].due_date" id="tasks[0].due_date" class="validate" value="2024-12-01" max="2026-01-07" min="1876-01-01" type="date" />
<div class="age-display" id="tasks[0].due_date_age" style="margin-top: 5px; font-size: 0.9em; color: #666;"></div>
<script>
document.addEventListener('DOMContentLoaded', function() {
const birthdateField = document.getElementById('tasks[0].due_date');
const ageDisplay = document.getElementById('tasks[0].due_date_age');
function calculateAge() {
if (birthdateField.value && ageDisplay) {
const birthDate = new Date(birthdateField.value);
const today = new Date();
let age = today.getFullYear() - birthDate.getFullYear();
const monthDiff = today.getMonth() - birthDate.getMonth();
if (monthDiff < 0 || (monthDiff === 0 && today.getDate() < birthDate.getDate())) {
age--;
}
ageDisplay.textContent = age >= 0 ? `Age: ${age} years` : '';
}
}
if (birthdateField) {
birthdateField.addEventListener('change', calculateAge);
calculateAge(); // Calculate on load if value is set
}
});
</script>
</div>
<div class="help-text">When should this be completed? (optional)</div></div>
</div>
<div class="col-md-6">
<div class="input-field col s12"><label for="tasks[0].completed"><i class="material-icons">check</i> Completed</label>
<input name="tasks[0].completed" id="tasks[0].completed" class="filled-in" value="1" type="checkbox" />
<div class="help-text">Is this task done?</div></div>
</div></div>
</div>
</div>
</div>
<div class="model-list-item card border mb-3"
data-index="1"
data-title-template="Item #{index}"
data-field-name="tasks">
<div class="card-header d-flex justify-content-between align-items-center">
<h6 class="mb-0">
<button class="btn btn-link text-decoration-none p-0 text-start"
type="button"
data-bs-toggle="collapse"
data-bs-target="#tasks_item_1_content"
aria-expanded="true"
aria-controls="tasks_item_1_content">
<i class="bi bi-chevron-down me-2"></i>
<i class="bi bi-card-list me-2"></i>
Item #2
</button>
</h6>
<button type="button"
class="btn btn-outline-danger btn-sm remove-item-btn"
data-index="1"
data-field-name="tasks"
title="Remove this item">
<i class="bi bi-trash"></i>
</button>
</div>
<div class="collapse show" id="tasks_item_1_content">
<div class="card-body"><div class="row">
<div class="col-md-6">
<div class="input-field col s12"><label for="tasks[1].task_name"><i class="material-icons">check_box</i> Task Description</label>
<input name="tasks[1].task_name" id="tasks[1].task_name" class="validate" value="Design mockups and wireframes" minlength="1" maxlength="11" placeholder="Enter task description..." pattern="\d{3}-\d{2}-\d{4}" inputmode="numeric" autocomplete="off" type="text" />
<div class="help-text">What needs to be done?</div></div>
</div>
<div class="col-md-6">
<div class="input-field col s12"><div class="md-field">
<div class="md-field-with-icon">
<span class="md-icon material-icons">warning</span>
<div class="md-input-wrapper">
<select name="tasks[1].priority" id="tasks[1].priority" class="browser-default"><option value="low">π’ Low</option>
<option value="medium" selected>π‘ Medium</option>
<option value="high">π High</option>
<option value="urgent">π΄ Urgent</option></select>
<label class="md-floating-label" for="tasks[1].priority">Priority</label>
</div>
</div>
<div class="md-help-text">How important is this task?</div>
</div></div>
</div>
<div class="col-md-6">
<div class="input-field col s12"><label for="tasks[1].due_date"><i class="material-icons">event</i> Due Date</label>
<div class="birthdate-input-group"><input name="tasks[1].due_date" id="tasks[1].due_date" class="validate" value="2024-12-15" max="2026-01-07" min="1876-01-01" type="date" />
<div class="age-display" id="tasks[1].due_date_age" style="margin-top: 5px; font-size: 0.9em; color: #666;"></div>
<script>
document.addEventListener('DOMContentLoaded', function() {
const birthdateField = document.getElementById('tasks[1].due_date');
const ageDisplay = document.getElementById('tasks[1].due_date_age');
function calculateAge() {
if (birthdateField.value && ageDisplay) {
const birthDate = new Date(birthdateField.value);
const today = new Date();
let age = today.getFullYear() - birthDate.getFullYear();
const monthDiff = today.getMonth() - birthDate.getMonth();
if (monthDiff < 0 || (monthDiff === 0 && today.getDate() < birthDate.getDate())) {
age--;
}
ageDisplay.textContent = age >= 0 ? `Age: ${age} years` : '';
}
}
if (birthdateField) {
birthdateField.addEventListener('change', calculateAge);
calculateAge(); // Calculate on load if value is set
}
});
</script>
</div>
<div class="help-text">When should this be completed? (optional)</div></div>
</div>
<div class="col-md-6">
<div class="input-field col s12"><label for="tasks[1].completed"><i class="material-icons">check</i> Completed</label>
<input name="tasks[1].completed" id="tasks[1].completed" class="filled-in" value="1" type="checkbox" />
<div class="help-text">Is this task done?</div></div>
</div></div>
</div>
</div>
</div>
</div>
<div class="md-model-list-actions">
<button type="button" class="md-button md-button-tonal add-item-btn" data-target="tasks">
<svg class="material-icons md-button__icon" viewBox="0 0 24 24" focusable="false" aria-hidden="true"><path d="M19 13H13v6h-2v-6H5v-2h6V5h2v6h6v2z" /></svg>
<span class="md-button__label">Add Item</span>
</button>
</div>
</div>
<p class="md-help-text">Manage your tasks (dynamic list demonstration)</p>
</section>
</div>
</div>
</div>
</div>
</section>
</div>
</div>
</div>
<script>
function switchTab(tabId, buttonElement) {
const tabLayout = buttonElement.closest('.tab-layout');
const panels = tabLayout.querySelectorAll('.tab-panel');
const buttons = tabLayout.querySelectorAll('.tab-button');
panels.forEach(panel => {
panel.style.display = 'none';
panel.setAttribute('aria-hidden', 'true');
});
buttons.forEach(button => {
button.classList.remove('active');
button.setAttribute('aria-selected', 'false');
});
const selectedPanel = document.getElementById(tabId);
if (selectedPanel) {
selectedPanel.style.display = 'block';
selectedPanel.setAttribute('aria-hidden', 'false');
}
buttonElement.classList.add('active');
buttonElement.setAttribute('aria-selected', 'true');
}
</script>
<style>
.md-form-container .tab-layout {
border-radius: 28px !important;
background: #fff !important;
border: 1px solid #e7e0ec !important;
padding: 16px 24px !important;
margin-bottom: 28px !important;
box-shadow: 0 2px 6px rgba(0,0,0,0.08) !important;
}
.md-form-container .tab-layout .tab-navigation {
display: flex !important;
gap: 4px !important;
border-bottom: 1px solid #e7e0ec !important;
margin-bottom: 16px !important;
}
.md-form-container .tab-layout .tab-button {
border: none !important;
background: transparent !important;
color: #49454f !important;
font-weight: 500 !important;
padding: 0.75rem 1rem !important;
border-bottom: 2px solid transparent !important;
border-radius: 0 !important;
transition: color 0.15s ease, border-color 0.15s ease !important;
}
.md-form-container .tab-layout .tab-button.active {
color: #6750a4 !important;
border-bottom-color: #6750a4 !important;
font-weight: 600 !important;
}
.md-form-container .tab-layout .tab-button:hover {
background: rgba(103, 80, 164, 0.08) !important;
}
.md-form-container .tab-layout .tab-panel {
padding: 8px 0 !important;
}
</style>
<div class="md-field">
<button type="submit" class="md-button md-button-filled">Submit</button>
</div>
</form>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
// Material Design 3 form enhancements
// Floating label functionality for outlined text fields
function initializeFloatingLabels() {
const textFields = document.querySelectorAll('.md-input, .md-textarea, .md-select');
textFields.forEach(input => {
const label = input.nextElementSibling;
if (label && label.classList.contains('md-floating-label')) {
// Check initial state
function updateLabelState() {
const hasValue = input.value && input.value.trim() !== '';
const isFocused = document.activeElement === input;
if (hasValue || isFocused) {
label.style.transform = 'translateY(-28px) scale(0.75)';
label.style.color = isFocused ? '#6750a4' : '#49454f';
label.style.background = '#ffffff';
label.style.padding = '0 4px';
} else {
label.style.transform = 'translateY(0) scale(1)';
label.style.color = '#49454f';
label.style.background = 'transparent';
label.style.padding = '0';
}
}
// Set up event listeners
input.addEventListener('focus', updateLabelState);
input.addEventListener('blur', updateLabelState);
input.addEventListener('input', updateLabelState);
// Initial state check
updateLabelState();
}
});
}
// Enhanced focus and blur effects
const inputs = document.querySelectorAll('.md-input, .md-select, .md-textarea');
inputs.forEach(input => {
input.addEventListener('focus', function() {
this.style.transform = 'scale(1.01)';
this.style.transition = 'all 0.15s cubic-bezier(0.4, 0, 0.2, 1)';
});
input.addEventListener('blur', function() {
this.style.transform = 'scale(1)';
});
});
// Checkbox interactions with Material Design ripple effect
const checkboxes = document.querySelectorAll('.md-checkbox');
checkboxes.forEach(checkbox => {
checkbox.addEventListener('change', function() {
const container = this.closest('.md-checkbox-container');
if (this.checked) {
// Create ripple effect
const ripple = document.createElement('div');
ripple.style.position = 'absolute';
ripple.style.borderRadius = '50%';
ripple.style.background = 'rgba(103, 80, 164, 0.3)';
ripple.style.width = '40px';
ripple.style.height = '40px';
ripple.style.left = '-11px';
ripple.style.top = '-11px';
ripple.style.pointerEvents = 'none';
ripple.style.transform = 'scale(0)';
ripple.style.transition = 'transform 0.3s cubic-bezier(0.4, 0, 0.2, 1)';
this.style.position = 'relative';
this.appendChild(ripple);
// Animate ripple
setTimeout(() => {
ripple.style.transform = 'scale(1)';
setTimeout(() => {
ripple.style.opacity = '0';
setTimeout(() => {
if (ripple.parentNode) {
ripple.parentNode.removeChild(ripple);
}
}, 300);
}, 200);
}, 10);
}
});
});
// Enhanced form validation with Material Design styling
const form = document.querySelector('.md-form');
if (form) {
form.addEventListener('submit', function(e) {
const requiredInputs = this.querySelectorAll('input[required], select[required], textarea[required]');
let hasErrors = false;
requiredInputs.forEach(input => {
const value = input.type === 'checkbox' ? input.checked : input.value.trim();
const fieldContainer = input.closest('.md-field');
if (!value) {
input.classList.add('error');
// Add error styling to label
const label = input.nextElementSibling;
if (label && label.classList.contains('md-floating-label')) {
label.style.color = '#ba1a1a';
}
// Create or update error message
let errorDiv = fieldContainer.querySelector('.md-error-text');
if (!errorDiv) {
errorDiv = document.createElement('div');
errorDiv.className = 'md-error-text';
fieldContainer.appendChild(errorDiv);
}
errorDiv.textContent = 'This field is required';
hasErrors = true;
} else {
input.classList.remove('error');
// Remove error styling from label
const label = input.nextElementSibling;
if (label && label.classList.contains('md-floating-label')) {
label.style.color = input === document.activeElement ? '#6750a4' : '#49454f';
}
// Remove error message if it was dynamically added
const errorDiv = fieldContainer.querySelector('.md-error-text');
if (errorDiv && errorDiv.textContent === 'This field is required') {
errorDiv.remove();
}
}
});
if (hasErrors) {
e.preventDefault();
// Scroll to first error with smooth animation
const firstError = this.querySelector('.error');
if (firstError) {
firstError.scrollIntoView({
behavior: 'smooth',
block: 'center',
inline: 'nearest'
});
// Focus the field for better UX
setTimeout(() => {
firstError.focus();
}, 500);
}
}
});
// Real-time validation for better UX
const allInputs = form.querySelectorAll('input, select, textarea');
allInputs.forEach(input => {
input.addEventListener('blur', function() {
if (this.hasAttribute('required')) {
const value = this.type === 'checkbox' ? this.checked : this.value.trim();
const fieldContainer = this.closest('.md-field');
if (!value) {
this.classList.add('error');
const label = this.nextElementSibling;
if (label && label.classList.contains('md-floating-label')) {
label.style.color = '#ba1a1a';
}
} else {
this.classList.remove('error');
const label = this.nextElementSibling;
if (label && label.classList.contains('md-floating-label')) {
label.style.color = '#49454f';
}
}
}
});
});
}
# Initialize floating labels
initializeFloatingLabels();
# Reinitialize for dynamically added content
window.reinitializeMaterialForms = function() {
initializeFloatingLabels();
};
});
</script>
<script>
(function() {
document.addEventListener('DOMContentLoaded', function() {
// Prevent Enter key from submitting forms unless on submit button
const forms = document.querySelectorAll('form.md-form, form.pydantic-form');
forms.forEach(function(form) {
form.addEventListener('keydown', function(e) {
// Check if Enter key is pressed
if (e.key === 'Enter' || e.keyCode === 13) {
const target = e.target;
// Allow Enter in textareas (for multi-line input)
if (target.tagName === 'TEXTAREA') {
return;
}
// Allow Enter on submit buttons
if (target.tagName === 'BUTTON' && target.type === 'submit') {
return;
}
// Allow Enter on input type="submit"
if (target.tagName === 'INPUT' && target.type === 'submit') {
return;
}
// Prevent form submission for all other cases
e.preventDefault();
return false;
}
});
});
});
})();
</script>
class LayoutDemonstrationForm(FormModel):
"""
Create a tabbed layout using VerticalLayout, HorizontalLayout, TabbedLayout, and ListLayout as the tabs.
This demonstrates how to use layout classes as field types in a Pydantic form.
Each field represents a different layout type that can be rendered as a tab.
"""
vertical_tab: VerticalFormLayout = FormField(
VerticalFormLayout(),
title="Personal Info",
input_type="layout",
help_text="Vertical layout demonstration"
)
horizontal_tab: HorizontalFormLayout = FormField(
HorizontalFormLayout(),
title="Contact Info",
input_type="layout",
help_text="Horizontal layout demonstration"
)
tabbed_tab: TabbedFormLayout = FormField(
TabbedFormLayout(),
title="Preferences",
input_type="layout",
help_text="Tabbed layout demonstration"
)
list_tab: ListFormLayout = FormField(
ListFormLayout(),
title="Task List",
input_type="layout",
help_text="List layout demonstration"
)
{
"description": "Create a tabbed layout using VerticalLayout, HorizontalLayout, TabbedLayout, and ListLayout as the tabs.\n\nThis demonstrates how to use layout classes as field types in a Pydantic form.\nEach field represents a different layout type that can be rendered as a tab.",
"properties": {
"vertical_tab": {
"additionalProperties": true,
"autofocus": false,
"description": "Vertical layout demonstration",
"disabled": false,
"help_text": "Vertical layout demonstration",
"input_type": "layout",
"readonly": false,
"title": "Personal Info",
"type": "object"
},
"horizontal_tab": {
"additionalProperties": true,
"autofocus": false,
"description": "Horizontal layout demonstration",
"disabled": false,
"help_text": "Horizontal layout demonstration",
"input_type": "layout",
"readonly": false,
"title": "Contact Info",
"type": "object"
},
"tabbed_tab": {
"additionalProperties": true,
"autofocus": false,
"description": "Tabbed layout demonstration",
"disabled": false,
"help_text": "Tabbed layout demonstration",
"input_type": "layout",
"readonly": false,
"title": "Preferences",
"type": "object"
},
"list_tab": {
"additionalProperties": true,
"autofocus": false,
"description": "List layout demonstration",
"disabled": false,
"help_text": "List layout demonstration",
"input_type": "layout",
"readonly": false,
"title": "Task List",
"type": "object"
}
},
"title": "LayoutDemonstrationForm",
"type": "object"
}{
"vertical_tab": {
"required": false,
"type": "object"
},
"horizontal_tab": {
"required": false,
"type": "object"
},
"tabbed_tab": {
"required": false,
"type": "object"
},
"list_tab": {
"required": false,
"type": "object"
}
}{
"errors": {},
"data": {
"vertical_tab": {
"first_name": "Alex",
"last_name": "Johnson",
"email": "alex.johnson@example.com",
"birth_date": "1990-05-15"
},
"horizontal_tab": {
"phone": "+1 (555) 987-6543",
"address": "456 Demo Street",
"city": "San Francisco",
"postal_code": "94102"
},
"tabbed_tab": {
"notification_email": true,
"notification_sms": false,
"theme": "dark",
"language": "en"
},
"list_tab": {
"project_name": "Website Redesign Project",
"tasks": [
{
"task_name": "Complete project setup and requirements gathering",
"priority": "high",
"due_date": "2024-12-01",
"completed": false
},
{
"task_name": "Design mockups and wireframes",
"priority": "medium",
"due_date": "2024-12-15",
"completed": false
}
]
}
}
}