Adding Input Group, cleaning up other new components

This commit is contained in:
Ronan Berder
2025-11-03 11:51:39 +08:00
parent ab44d6d48a
commit 0c16664e4b
5 changed files with 848 additions and 283 deletions
+166 -97
View File
@@ -29,9 +29,9 @@ toc:
{% set code_demo %}
<form class="w-full max-w-md space-y-6">
<fieldset class="fieldset" aria-describedby="payment-desc">
<fieldset class="fieldset">
<legend>Payment Method</legend>
<p id="payment-desc" class="description">All transactions are secure and encrypted</p>
<p>All transactions are secure and encrypted</p>
<div role="group" class="field">
<label for="card-name">Name on Card</label>
@@ -47,7 +47,7 @@ toc:
<div class="grid grid-cols-3 gap-4">
<div role="group" class="field">
<label for="exp-month">Month</label>
<select id="exp-month" class="select">
<select id="exp-month" class="select w-full">
<option value="">MM</option>
<option value="01">01</option>
<option value="02">02</option>
@@ -65,7 +65,7 @@ toc:
</div>
<div role="group" class="field">
<label for="exp-year">Year</label>
<select id="exp-year" class="select">
<select id="exp-year" class="select w-full">
<option value="">YYYY</option>
<option value="2024">2024</option>
<option value="2025">2025</option>
@@ -84,13 +84,15 @@ toc:
<hr role="separator" class="my-6 border-border">
<fieldset class="fieldset" aria-describedby="billing-desc">
<fieldset class="fieldset">
<legend>Billing Address</legend>
<p id="billing-desc" class="description">The billing address associated with your payment method</p>
<p>The billing address associated with your payment method</p>
<div class="flex items-center gap-3">
<input type="checkbox" id="same-as-shipping" checked>
<label for="same-as-shipping" class="font-normal">Same as shipping address</label>
<div role="group" class="field">
<label for="same-as-shipping" class="gap-3">
<input type="checkbox" id="same-as-shipping" checked>
Same as shipping address
</label>
</div>
</fieldset>
@@ -101,7 +103,7 @@ toc:
</div>
</fieldset>
<div class="flex gap-2">
<div class="flex gap-3">
<button type="submit" class="btn">Submit</button>
<button type="button" class="btn-outline">Cancel</button>
</div>
@@ -116,10 +118,10 @@ toc:
<p>Field provides semantic HTML patterns for accessible forms:</p>
<ul>
<li><code>&lt;fieldset&gt;</code> and <code>&lt;legend&gt;</code> for grouping related fields</li>
<li><code>role="group"</code> with <code>.field</code> class for individual fields</li>
<li><code>.description</code> class for helper text</li>
<li><code>&lt;p&gt;</code> after <code>&lt;legend&gt;</code> is automatically styled as description text</li>
<li><code>role="group"</code> with <code>.field</code> class for individual field containers</li>
<li><code>.description</code> class for field-level helper text (requires <code>aria-describedby</code>)</li>
<li><code>.error</code> class with <code>role="alert"</code> for error messages</li>
<li><code>aria-describedby</code> to associate descriptions and errors with inputs</li>
</ul>
</section>
@@ -130,53 +132,91 @@ toc:
<h3 id="example-input"><a href="#example-input">Input</a></h3>
{% set code_input %}
<div class="grid gap-6 w-full max-w-md">
<div class="grid gap-6">
<div role="group" class="field">
<label for="username">Username</label>
<input id="username" type="text" placeholder="evilrabbit" aria-describedby="username-desc">
<p id="username-desc" class="description">Choose a unique username for your account</p>
<p id="username-desc" class="description">Choose a unique username for your account.</p>
</div>
<div role="group" class="field">
<label for="password">Password</label>
<input id="password" type="password" placeholder="••••••••" aria-invalid="true" aria-describedby="password-desc password-error">
<p id="password-desc" class="description">Must be at least 8 characters</p>
<p id="password-error" class="error" role="alert">Password is too short</p>
<p id="password-desc" class="description">Must be at least 8 characters long.</p>
<input id="password" type="password" placeholder="••••••••" aria-describedby="password-desc">
</div>
</div>
{% endset %}
{{ code_preview("field-input", code_input) }}
{{ code_preview("field-input", code_input, class="w-full max-w-md") }}
<h3 id="example-textarea"><a href="#example-textarea">Textarea</a></h3>
{% set code_textarea %}
<div role="group" class="field w-full max-w-md">
<label for="bio">Bio</label>
<textarea id="bio" rows="4" placeholder="Tell us about yourself" aria-describedby="bio-desc"></textarea>
<p id="bio-desc" class="description">Brief description for your profile (max 200 characters)</p>
<div role="group" class="field">
<label for="bio">Feedback</label>
<textarea id="bio" rows="4" placeholder="Your feedback helps us improve..." aria-describedby="bio-desc"></textarea>
<p id="bio-desc" class="description">Share your thoughts about our service.</p>
</div>
{% endset %}
{{ code_preview("field-textarea", code_textarea) }}
{{ code_preview("field-textarea", code_textarea, class="w-full max-w-md") }}
<h3 id="example-select"><a href="#example-select">Select</a></h3>
{% set code_select %}
<div role="group" class="field w-full max-w-md">
<label for="country">Country</label>
<select id="country" class="select" aria-describedby="country-desc">
<option value="">Select a country</option>
<option value="us">United States</option>
<option value="uk">United Kingdom</option>
<option value="ca">Canada</option>
<option value="au">Australia</option>
<div role="group" class="field">
<label for="country">Department</label>
<select id="country" class="select w-full" aria-describedby="country-desc">
<option value="">Choose department</option>
<option value="engineering">Engineering</option>
<option value="design">Design</option>
<option value="marketing">Marketing</option>
<option value="sales">Sales</option>
<option value="support">Customer Support</option>
<option value="hr">Human Resources</option>
<option value="finance">Finance</option>
<option value="operations">Operations</option>
</select>
<p id="country-desc" class="description">Choose your country of residence</p>
<p id="country-desc" class="description">Select your department or area of work.</p>
</div>
{% endset %}
{{ code_preview("field-select", code_select) }}
{{ code_preview("field-select", code_select, class="w-full max-w-md") }}
<h3 id="example-slider"><a href="#example-slider">Slider</a></h3>
{% set code %}
<div role="group" class="field">
<label for="price-range">Price</label>
<p id="price-range-desc" class="description">Set your budget: $<span id="price-range-value">150</span></p>
<input id="price-range" type="range" min="0" max="500" value="150">
</div>
<script>
(() => {
const sliders = document.querySelectorAll('input[type="range"]');
if (!sliders) return;
const updateSlider = (el) => {
const min = parseFloat(el.min || 0);
const max = parseFloat(el.max || 100);
const value = parseFloat(el.value);
const percent = (max === min) ? 0 : ((value - min) / (max - min)) * 100;
el.style.setProperty('--slider-value', `${percent}%`);
};
sliders.forEach(slider => {
updateSlider(slider);
slider.addEventListener('input', (event) => updateSlider(event.target));
});
const priceSlider = document.getElementById('price-range');
const priceValue = document.getElementById('price-range-value');
priceSlider.addEventListener('input', (event) => priceValue.innerHTML = event.target.value);
})();
</script>
{% endset %}
{{ code_preview("slider", code, "w-full max-w-sm") }}
<h3 id="example-fieldset"><a href="#example-fieldset">Fieldset</a></h3>
@@ -186,9 +226,9 @@ toc:
{% set code_fieldset %}
<form class="w-full max-w-md">
<fieldset class="fieldset" aria-describedby="profile-desc">
<fieldset class="fieldset">
<legend>Profile</legend>
<p id="profile-desc" class="description">This information will be displayed on your profile</p>
<p>This information will be displayed on your profile</p>
<div role="group" class="field">
<label for="full-name">Full name</label>
@@ -217,54 +257,84 @@ toc:
<h3 id="example-checkbox"><a href="#example-checkbox">Checkbox</a></h3>
{% set code_checkbox %}
<fieldset class="fieldset w-full max-w-md" aria-describedby="tasks-desc">
<legend>Tasks</legend>
<p id="tasks-desc" class="description">Select the tasks you want to receive updates for</p>
<fieldset class="fieldset">
<legend>Show these items on the desktop</legend>
<p>Select the items you want to show on the desktop.</p>
<div class="flex flex-col gap-3">
<div class="flex items-center gap-3">
<input type="checkbox" id="push-notifications" checked>
<label for="push-notifications" class="font-normal">Push notifications</label>
<div class="field">
<label class="gap-3">
<input type="checkbox">
Hard disks
</label>
</div>
<div class="flex items-center gap-3">
<input type="checkbox" id="email-notifications" checked>
<label for="email-notifications" class="font-normal">Email notifications</label>
<div class="field">
<label class="gap-3">
<input type="checkbox">
External disks
</label>
</div>
<div class="flex items-center gap-3">
<input type="checkbox" id="sms-notifications">
<label for="sms-notifications" class="font-normal">SMS notifications</label>
<div class="field">
<label class="gap-3">
<input type="checkbox">
CDs, DVDs and iPods
</label>
</div>
<div class="field">
<label class="gap-3">
<input type="checkbox">
Connected servers
</label>
</div>
</div>
<hr role="separator">
<div class="field">
<div class="flex w-full gap-3 flex-row items-center">
<input id="sync-desktop-documents" type="checkbox" checked class="input self-start">
<div class="flex flex-1 flex-col gap-1.5 leading-snug">
<label for="sync-desktop-documents">Sync Desktop & Documents folders</label>
<p class="description">Your Desktop & Documents folders are being synced with iCloud Drive. You can access them from other devices.</p>
</div>
</div>
</div>
</fieldset>
{% endset %}
{{ code_preview("field-checkbox", code_checkbox) }}
{{ code_preview("field-checkbox", code_checkbox, class="w-full max-w-md") }}
<h3 id="example-radio"><a href="#example-radio">Radio Group</a></h3>
{% set code_radio %}
<fieldset class="fieldset w-full max-w-md" aria-describedby="notify-desc">
<legend>Notify me about</legend>
<p id="notify-desc" class="description">Choose which notifications you want to receive</p>
<fieldset class="fieldset">
<legend>Subscription plan</legend>
<p>Yearly and lifetime plans offer significant savings.</p>
<div class="flex flex-col gap-3">
<div class="flex items-center gap-3">
<input type="radio" id="notify-all" name="notifications" checked>
<label for="notify-all" class="font-normal">All new messages</label>
<div role="radiogroup" class="grid gap-3">
<div class="field">
<label class="gap-3">
<input type="radio" name="subscription-plan" checked>
Monthly ($9.99/month)
</label>
</div>
<div class="flex items-center gap-3">
<input type="radio" id="notify-direct" name="notifications">
<label for="notify-direct" class="font-normal">Direct messages and mentions</label>
<div class="field">
<label class="gap-3">
<input type="radio" name="subscription-plan">
Yearly ($99.99/year)
</label>
</div>
<div class="flex items-center gap-3">
<input type="radio" id="notify-none" name="notifications">
<label for="notify-none" class="font-normal">Nothing</label>
<div class="field">
<label class="gap-3">
<input type="radio" name="subscription-plan">
Lifetime ($299.99)
</label>
</div>
</div>
</fieldset>
{% endset %}
{{ code_preview("field-radio", code_radio) }}
{{ code_preview("field-radio", code_radio, class="w-full max-w-md") }}
<h3 id="example-switch"><a href="#example-switch">Switch</a></h3>
@@ -273,44 +343,43 @@ toc:
</section>
{% set code_switch %}
<fieldset class="fieldset w-full max-w-md">
<legend>Notifications</legend>
<p class="description">Configure how you receive notifications</p>
<div class="flex flex-col gap-4">
<div class="flex items-center justify-between">
<div>
<label for="marketing" class="font-medium">Marketing emails</label>
<p class="description text-sm">Receive emails about new products and features</p>
</div>
<input type="checkbox" id="marketing" class="toggle" checked>
</div>
<div class="flex items-center justify-between">
<div>
<label for="security" class="font-medium">Security emails</label>
<p class="description text-sm">Receive emails about account security</p>
</div>
<input type="checkbox" id="security" class="toggle" checked>
</div>
<div role="group" class="field flex-row">
<div class="flex flex-1 flex-col gap-1.5 leading-snug">
<label for="multi-factor-authentication">Multi-factor authentication</label>
<p class="description">Enable multi-factor authentication. If you do not have a two-factor device, you can use a one-time code sent to your email.</p>
</div>
</fieldset>
<input id="multi-factor-authentication" type="checkbox" role="switch">
</div>
{% endset %}
{{ code_preview("field-switch", code_switch) }}
{{ code_preview("field-switch", code_switch, class="w-full max-w-md") }}
<h2 id="accessibility"><a href="#accessibility">Accessibility</a></h2>
<section class="prose">
<ul>
<li>Always use <code>&lt;label&gt;</code> elements with <code>for</code> attribute matching input <code>id</code></li>
<li>Associate helper text and errors using <code>aria-describedby</code> on the input</li>
<li>Use <code>aria-invalid="true"</code> on inputs with errors</li>
<li>Use <code>role="alert"</code> on error messages for screen reader announcements</li>
<li>Group related fields with <code>&lt;fieldset&gt;</code> and <code>&lt;legend&gt;</code></li>
<li>Use <code>role="group"</code> for individual field wrappers</li>
</ul>
</section>
<h3 id="example-choice-card"><a href="#example-choice-card">Choice Card</a></h3>
{% set code_choice_card %}
<fieldset class="fieldset">
<legend class="text-sm">Compute Environment</legend>
<p>Select the compute environment for your cluster.</p>
<div role="group" class="field">
<label class="flex gap-3 border border-transparent rounded-md p-4 has-[:checked]:border-primary has-[:checked]:bg-primary/5 dark:has-[:checked]:bg-primary/10">
<div class="flex flex-1 flex-col gap-1.5 leading-snug">
<h3>Kubernetes</h3>
<p class="description text-">Run GPU workloads on a K8s configured cluster.</p>
</div>
<input id="choice-card" type="radio" name="choice-card" class="self-start" checked>
</label>
</div>
<div role="group" class="field">
<label class="flex gap-3 border border-transparent rounded-md p-4 has-[:checked]:border-primary has-[:checked]:bg-primary/5 dark:has-[:checked]:bg-primary/10">
<div class="flex flex-1 flex-col gap-1.5 leading-snug">
<h3>Virtual Machine</h3>
<p class="description text-">Access a VM configured cluster to run GPU workloads.</p>
</div>
<input id="choice-card" type="radio" name="choice-card" class="self-start">
</label>
</div>
</div>
{% endset %}
{{ code_preview("field-choice-card", code_choice_card, class="w-full max-w-md") }}
+259 -145
View File
@@ -1,247 +1,361 @@
---
layout: layouts/page.njk
title: Input Group
description: Display additional information or actions alongside inputs using simple HTML patterns.
description: Display additional information or actions to an input or textarea.
toc:
- label: Usage
id: usage
- label: Examples
id: examples
children:
- label: Icon Prefix
id: example-icon-prefix
- label: Icon Suffix
id: example-icon-suffix
- label: Text Prefix/Suffix
- label: Icon
id: example-icon
- label: Text
id: example-text
- label: Button Suffix
- label: Button
id: example-button
- label: Multiple Elements
id: example-multiple
- label: Tooltip
id: example-tooltip
- label: Textarea
id: example-textarea
- label: Spinner
id: example-spinner
- label: Dropdown
id: example-dropdown
- label: Group
id: example-group
---
{% from "macros/code_preview.njk" import code_preview %}
{% from "macros/code_block.njk" import code_block %}
{% from "dropdown-menu.njk" import dropdown_menu %}
{% from "popover.njk" import popover %}
<div class="alert mb-6">
{% lucide "circle-alert" %}
<h2>There is no dedicated Input Group component in Basecoat.</h2>
</div>
{% set code_default %}
<div class="relative">
<input type="text" class="input pl-10" placeholder="Search...">
<div class="absolute left-3 top-1/2 -translate-y-1/2 pointer-events-none text-muted-foreground">
{% lucide "search" %}
<div class="grid gap-6">
<div class="relative">
<input type="text" class="input pl-9 pr-20" placeholder="Search...">
<div class="absolute left-3 top-1/2 -translate-y-1/2 pointer-events-none text-muted-foreground [&>svg]:size-4">
{% lucide "search" %}
</div>
<div class="absolute right-3 top-1/2 -translate-y-1/2 pointer-events-none text-muted-foreground text-sm">12 results</div>
</div>
<div class="relative">
<input type="text" class="input pl-15 pr-9" placeholder="example.com">
<div class="absolute left-3 top-1/2 -translate-y-1/2 pointer-events-none text-muted-foreground text-sm">https://</div>
<div class="absolute right-3 top-1/2 -translate-y-1/2 text-muted-foreground [&>svg]:size-4" data-tooltip="This is content in a tooltip.">{% lucide "info" %}</div>
</div>
<div class="relative">
<textarea class="textarea pr-10 min-h-27 pb-12" placeholder="Ask, Search or Chat..."></textarea>
<footer role="group" class="absolute bottom-0 px-3 pb-3 pt-1.5 flex items-center w-full gap-2">
<button type="button" class="btn-icon-outline rounded-full size-6 text-muted-foreground hover:text-accent-foreground">
{% lucide "plus" %}
</button>
{{ dropdown_menu(
id="demo-dropdown-menu",
trigger="Auto",
trigger_attrs={"class": "btn-sm-ghost text-muted-foreground hover:text-accent-foreground h-6 p-2"},
popover_attrs={"data-side": "top", "class": "min-w-32"},
items=[
{ type: "item", label: "Auto" },
{ type: "item", label: "Agent" },
{ type: "item", label: "Manual" }
]
) }}
<div class="text-muted-foreground text-sm ml-auto">52% used</div>
<hr class="w-0 h-4 border-r border-border shrink-0 m-0">
<button type="button" class="btn-icon rounded-full size-6 bg-muted-foreground hover:bg-foreground" disabled>
{% lucide "arrow-up" %}
</button>
</footer>
</div>
<div class="relative">
<input type="text" class="input pr-9" placeholder="@shadcn">
<div class="absolute right-3 top-1/2 -translate-y-1/2 pointer-events-none bg-primary text-primary-foreground flex size-4 items-center justify-center rounded-full [&>svg]:size-3">{% lucide "check" %}</div>
</div>
</div>
{% endset %}
{{ code_preview("input-group-default", code_default) }}
{{ code_preview("input-group-default", code_default, class="w-full max-w-sm") }}
<h2 id="usage"><a href="#usage">Usage</a></h2>
<section class="prose">
<p>Input groups are achieved through composition using relative positioning and Tailwind utilities. No special component needed - just wrap your input with a positioned container and add your icons, text, or buttons.</p>
<p><strong>Key patterns:</strong></p>
<ul>
<li>Use <code>relative</code> wrapper around input</li>
<li>Position content with <code>absolute</code> + <code>top-1/2 -translate-y-1/2</code></li>
<li>Add padding to input (<code>pl-*</code> or <code>pr-*</code>) to make room for content</li>
<li>Use <code>pointer-events-none</code> for decorative icons</li>
</ul>
<p>Input groups are pure HTML composition using Tailwind utility classes. There is no dedicated component: use <code>relative</code> positioning on a wrapper, add your content with <code>absolute</code> positioning, and adjust input padding to make room.</p>
<p>Browse the examples below and copy the pattern that fits your needs.</p>
</section>
{{ code_block(code_default) }}
<h2 id="examples"><a href="#examples">Examples</a></h2>
<h3 id="example-icon-prefix"><a href="#example-icon-prefix">Icon Prefix</a></h3>
<h3 id="example-icon"><a href="#example-icon">Icon</a></h3>
<section class="prose">
<p>Add an icon at the start of the input field.</p>
</section>
{% set code_icon_prefix %}
<div class="flex flex-col gap-4 w-full max-w-sm">
{% set code_icon %}
<div class="grid gap-6">
<div class="relative">
<input type="text" class="input pl-10" placeholder="Search...">
<div class="absolute left-3 top-1/2 -translate-y-1/2 pointer-events-none text-muted-foreground">
<input type="text" class="input pl-9" placeholder="Search...">
<div class="absolute left-3 top-1/2 -translate-y-1/2 pointer-events-none text-muted-foreground [&>svg]:size-4">
{% lucide "search" %}
</div>
</div>
<div class="relative">
<input type="email" class="input pl-10" placeholder="Email">
<div class="absolute left-3 top-1/2 -translate-y-1/2 pointer-events-none text-muted-foreground">
<input type="email" class="input pl-9" placeholder="Enter your email">
<div class="absolute left-3 top-1/2 -translate-y-1/2 pointer-events-none text-muted-foreground [&>svg]:size-4">
{% lucide "mail" %}
</div>
</div>
</div>
{% endset %}
{{ code_preview("input-group-icon-prefix", code_icon_prefix) }}
<h3 id="example-icon-suffix"><a href="#example-icon-suffix">Icon Suffix</a></h3>
<section class="prose">
<p>Add an icon at the end of the input field.</p>
</section>
{% set code_icon_suffix %}
<div class="flex flex-col gap-4 w-full max-w-sm">
<div class="relative">
<input type="text" class="input pr-10" placeholder="Username">
<div class="absolute right-3 top-1/2 -translate-y-1/2 pointer-events-none text-green-500">
<input type="text" class="input px-9" placeholder="Card number">
<div class="absolute left-3 top-1/2 -translate-y-1/2 pointer-events-none text-muted-foreground [&>svg]:size-4">
{% lucide "credit-card" %}
</div>
<div class="absolute right-3 top-1/2 -translate-y-1/2 pointer-events-none text-muted-foreground [&>svg]:size-4">
{% lucide "check" %}
</div>
</div>
<div class="relative">
<input type="text" class="input pr-16" placeholder="Card number">
<div class="absolute right-3 top-1/2 -translate-y-1/2 flex items-center gap-1 pointer-events-none text-muted-foreground">
{% lucide "credit-card" %}
<div class="absolute right-3 top-1/2 -translate-y-1/2 pointer-events-none text-muted-foreground [&>svg]:size-4 flex items-center gap-2">
{% lucide "star" %}
{% lucide "info" %}
</div>
</div>
</div>
{% endset %}
{{ code_preview("input-group-icon-suffix", code_icon_suffix) }}
{{ code_preview("input-group-icon-prefix", code_icon, class="w-full max-w-sm") }}
<h3 id="example-text"><a href="#example-text">Text Prefix/Suffix</a></h3>
<section class="prose">
<p>Display text before or after the input value.</p>
</section>
<h3 id="example-text"><a href="#example-text">Text</a></h3>
{% set code_text %}
<div class="flex flex-col gap-4 w-full max-w-sm">
<div class="grid gap-6">
<div class="relative">
<input type="text" class="input pl-12 pr-16" placeholder="0.00">
<div class="absolute left-3 top-1/2 -translate-y-1/2 pointer-events-none text-muted-foreground text-sm">
$
</div>
<div class="absolute right-3 top-1/2 -translate-y-1/2 pointer-events-none text-muted-foreground text-sm">
USD
</div>
<input type="text" class="input pl-7 pr-12" placeholder="0.00">
<div class="absolute left-3 top-1/2 -translate-y-1/2 pointer-events-none text-muted-foreground text-sm">$</div>
<div class="absolute right-3 top-1/2 -translate-y-1/2 pointer-events-none text-muted-foreground text-sm">USD</div>
</div>
<div class="relative">
<input type="text" class="input pl-20" placeholder="example.com">
<div class="absolute left-3 top-1/2 -translate-y-1/2 pointer-events-none text-muted-foreground text-sm">
https://
</div>
<input type="text" class="input pl-15 pr-12" placeholder="example.com">
<div class="absolute left-3 top-1/2 -translate-y-1/2 pointer-events-none text-muted-foreground text-sm">https://</div>
<div class="absolute right-3 top-1/2 -translate-y-1/2 pointer-events-none text-muted-foreground text-sm">.com</div>
</div>
<div class="relative">
<input type="text" class="input pr-32" placeholder="username">
<div class="absolute right-3 top-1/2 -translate-y-1/2 pointer-events-none text-muted-foreground text-sm">
@company.com
</div>
<input type="text" class="input pr-30" placeholder="Enter your username">
<div class="absolute right-3 top-1/2 -translate-y-1/2 pointer-events-none text-muted-foreground text-sm">@company.com</div>
</div>
<div class="relative">
<textarea class="textarea pr-10 min-h-25 pb-12" placeholder="Enter your message"></textarea>
<footer role="group" class="absolute bottom-0 px-3 pb-3 pt-1.5 flex items-center w-full gap-2">
<div class="text-muted-foreground text-sm">120 characters left</div>
</footer>
</div>
</div>
{% endset %}
{{ code_preview("input-group-text", code_text) }}
{{ code_preview("input-group-text", code_text, class="w-full max-w-sm") }}
<h3 id="example-button"><a href="#example-button">Button Suffix</a></h3>
<section class="prose">
<p>Add interactive buttons inside the input.</p>
</section>
<h3 id="example-button"><a href="#example-button">Button</a></h3>
{% set code_button %}
<div class="flex flex-col gap-4 w-full max-w-sm">
<div class="grid gap-6">
<div class="relative">
<input type="text" class="input pr-10" placeholder="Search...">
<button type="button" class="absolute right-1 top-1/2 -translate-y-1/2 btn-icon-sm-ghost">
{% lucide "search" %}
</button>
<input type="text" readonly class="input pr-9" value="https://x.com/hunvreus">
<button class="absolute right-1.5 top-1/2 -translate-y-1/2 btn-icon-ghost text-muted-foreground hover:text-accent-foreground size-6">{% lucide "copy" %}</button>
</div>
<div class="relative">
<input type="password" class="input pr-10" placeholder="Password">
<button type="button" class="absolute right-1 top-1/2 -translate-y-1/2 btn-icon-sm-ghost">
{% lucide "eye" %}
</button>
<input type="text" class="input pl-21 pr-9 rounded-full">
<button
aria-pressed="false"
onclick="this.ariaPressed = this.ariaPressed === 'true' ? 'false' : 'true'"
class="absolute right-1.5 top-1/2 -translate-y-1/2 btn-icon-ghost text-muted-foreground hover:text-accent-foreground size-6 rounded-full aria-pressed:bg-transparent aria-pressed:hover:bg-accent dark:aria-pressed:hover:bg-accent/50 aria-pressed:text-blue-600 aria-pressed:[&>svg]:fill-blue-600"
>{% lucide "star" %}</button>
<div class="absolute left-1.5 top-1/2 -translate-y-1/2 flex items-center gap-1 z-10">
{% set trigger %}
{% lucide "info" %}
{% endset %}
{% call popover(
trigger=trigger,
trigger_attrs={"class": "btn-sm-icon-ghost rounded-full size-6"},
popover_attrs={"class": "max-w-72"}
) %}
<h3 class="font-medium mb-1">Your connection is not secure.</h3>
<p>You should not enter any sensitive information on this site.</p>
{% endcall %}
<div class="text-muted-foreground text-sm pointer-events-none">https://</div>
</div>
</div>
<div class="relative">
<input type="text" class="input pr-10" value="https://example.com/page">
<button type="button" class="absolute right-1 top-1/2 -translate-y-1/2 btn-icon-sm-ghost">
{% lucide "copy" %}
</button>
<input type="text" readonly class="input pr-9" placeholder="Type to search...">
<button type="button" class="absolute right-1.5 top-1/2 -translate-y-1/2 btn-secondary h-6 p-2 rounded">Search</button>
</div>
</div>
{% endset %}
{{ code_preview("input-group-button", code_button) }}
{{ code_preview("input-group-button", code_button, class="w-full max-w-sm") }}
<h3 id="example-multiple"><a href="#example-multiple">Multiple Elements</a></h3>
<h3 id="example-tooltip"><a href="#example-tooltip">Tooltip</a></h3>
<section class="prose">
<p>Combine multiple icons, text, and buttons.</p>
</section>
{% set code_multiple %}
<div class="flex flex-col gap-4 w-full max-w-sm">
{% set code_tooltip %}
<div class="grid gap-6">
<div class="relative">
<input type="text" class="input pl-10 pr-20" placeholder="Search...">
<div class="absolute left-3 top-1/2 -translate-y-1/2 pointer-events-none text-muted-foreground">
{% lucide "search" %}
</div>
<div class="absolute right-3 top-1/2 -translate-y-1/2 flex items-center gap-1.5">
<span class="text-xs text-muted-foreground pointer-events-none">⌘K</span>
</div>
<input type="password" class="input pr-9" placeholder="Enter password">
<div class="absolute right-3 top-1/2 -translate-y-1/2 text-muted-foreground [&>svg]:size-4" data-tooltip="Password must be at least 8 characters long.">{% lucide "info" %}</div>
</div>
<div class="relative">
<input type="text" class="input pl-20 pr-10" placeholder="example.com">
<div class="absolute left-3 top-1/2 -translate-y-1/2 pointer-events-none text-muted-foreground text-sm">
https://
</div>
<button type="button" class="absolute right-1 top-1/2 -translate-y-1/2 btn-icon-sm-ghost">
{% lucide "info" %}
</button>
<input type="email" class="input pr-9" placeholder="Your email address">
<div class="absolute right-3 top-1/2 -translate-y-1/2 text-muted-foreground [&>svg]:size-4" data-tooltip="We'll use this to send your notifications.">{% lucide "circle-help" %}</div>
</div>
<div class="relative">
<input type="email" class="input pl-9" placeholder="Your email address">
<div class="absolute left-3 top-1/2 -translate-y-1/2 text-muted-foreground [&>svg]:size-4" data-tooltip="Click for help with API keys." data-side="left">{% lucide "circle-help" %}</div>
</div>
</div>
{% endset %}
{{ code_preview("input-group-multiple", code_multiple) }}
{{ code_preview("input-group-tooltip", code_tooltip, class="w-full max-w-sm") }}
<h3 id="example-textarea"><a href="#example-textarea">Textarea</a></h3>
<section class="prose">
<p>Add buttons to textareas for actions like submit.</p>
</section>
{% set code_textarea %}
<div class="flex flex-col gap-4 w-full max-w-sm">
<div class="relative">
<textarea class="textarea pr-10 min-h-24" placeholder="Enter your message..."></textarea>
<button type="button" class="absolute right-2 bottom-2 btn-icon-sm">
{% lucide "send" %}
<div class="relative">
<textarea class="textarea pt-15 pb-17 min-h-77" placeholder="console.log('Hello, world!')."></textarea>
<header class="absolute top-0 flex items-center w-full gap-2 p-3 border-b">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="tabler-icon tabler-icon-brand-javascript size-4 text-muted-foreground"><path d="M20 4l-2 14.5l-6 2l-6 -2l-2 -14.5z"></path><path d="M7.5 8h3v8l-2 -1"></path><path d="M16.5 8h-2.5a.5 .5 0 0 0 -.5 .5v3a.5 .5 0 0 0 .5 .5h1.423a.5 .5 0 0 1 .495 .57l-.418 2.93l-2 .5"></path></svg>
<span class="font-mono text-sm text-muted-foreground mr-auto">script.js</span>
<button class="btn-sm-icon-ghost text-muted-foreground hover:text-accent-foreground size-6">
{% lucide "refresh-ccw" %}
</button>
<button class="btn-sm-icon-ghost text-muted-foreground hover:text-accent-foreground size-6">
{% lucide "copy" %}
</button>
</header>
<footer class="absolute bottom-0 flex items-center w-full gap-2 p-3 border-t">
<span class="text-sm text-muted-foreground mr-auto">Line 1, Column 1</span>
<button type="button" class="btn-sm">
Run
{% lucide "corner-down-left" %}
</button>
</footer>
</div>
{% endset %}
{{ code_preview("input-group-textarea", code_textarea, class="w-full max-w-md") }}
<h3 id="example-spinner"><a href="#example-spinner">Spinner</a></h3>
{% set code_spinner %}
<div class="grid gap-6">
<div class="relative">
<input type="text" class="input pr-9" placeholder="Searching..." disabled>
<div class="absolute right-3 top-1/2 -translate-y-1/2 pointer-events-none text-muted-foreground [&>svg]:size-4 animate-spin opacity-50">
{% lucide "loader-circle" %}
</div>
</div>
<div class="relative">
<textarea class="textarea pb-10 min-h-32" placeholder="Type something..."></textarea>
<div class="absolute bottom-3 left-3 right-3 flex items-center justify-between gap-2">
<span class="text-xs text-muted-foreground">120 characters left</span>
<button type="button" class="btn-sm">Submit</button>
<input type="text" class="input pl-9" placeholder="Processing..." disabled>
<div class="absolute left-3 top-1/2 -translate-y-1/2 pointer-events-none text-muted-foreground [&>svg]:size-4 animate-spin opacity-50">
{% lucide "loader-circle" %}
</div>
</div>
<div class="relative">
<input type="text" class="input pr-24" placeholder="Saving changes..." disabled>
<div class="absolute right-3 top-1/2 -translate-y-1/2 flex items-center gap-2 text-sm text-muted-foreground opacity-50">
<div>Saving...</div>
<div class="[&>svg]:size-4 animate-spin">
{% lucide "loader-circle" %}
</div>
</div>
</div>
<div class="relative">
<input type="text" class="input pl-9 pr-26" placeholder="Refreshing data..." disabled>
<div class="absolute left-3 top-1/2 -translate-y-1/2 pointer-events-none text-muted-foreground [&>svg]:size-4 animate-spin opacity-50">
{% lucide "loader" %}
</div>
<div class="absolute right-3 top-1/2 -translate-y-1/2 flex items-center gap-2 text-sm text-muted-foreground opacity-50">
Please wait...
</div>
</div>
</div>
{% endset %}
{{ code_preview("input-group-textarea", code_textarea) }}
{{ code_preview("input-group-spinner", code_spinner, class="w-full max-w-md") }}
<h2 id="accessibility"><a href="#accessibility">Accessibility</a></h2>
<h3 id="example-dropdown"><a href="#example-dropdown">Dropdown</a></h3>
<section class="prose">
<ul>
<li>Use <code>pointer-events-none</code> for decorative icons that shouldn't be interactive</li>
<li>Ensure buttons have proper <code>aria-label</code> or visible text</li>
<li>Maintain sufficient padding so text doesn't overlap with icons/buttons</li>
<li>Use semantic button types (<code>type="button"</code> vs <code>type="submit"</code>)</li>
</ul>
</section>
{% set code_dropdown %}
<div class="grid gap-6">
<div class="relative">
<input type="text" class="input pr-9" placeholder="Enter file name">
{% set trigger %}
{% lucide "ellipsis" %}
{% endset %}
{{ dropdown_menu(
main_attrs={"class": "absolute right-1.5 top-1/2 -translate-y-1/2"},
trigger=trigger,
trigger_attrs={"class": "btn-sm-icon-ghost text-muted-foreground hover:text-accent-foreground size-6"},
popover_attrs={"data-align": "end", "class": "min-w-32"},
items=[
{ type: "item", label: "Settings" },
{ type: "item", label: "Copy path" },
{ type: "item", label: "Open location" }
]
) }}
</div>
<div class="relative">
<input type="text" class="input pr-30" placeholder="Enter search query">
{% set trigger %}
Search in...
{% lucide "chevron-down", { "class": "size-3" } %}
{% endset %}
{{ dropdown_menu(
main_attrs={"class": "absolute right-1.5 top-1/2 -translate-y-1/2"},
trigger=trigger,
trigger_attrs={"class": "btn-sm-ghost text-muted-foreground hover:text-accent-foreground p-2 h-6"},
popover_attrs={"data-align": "end", "class": "min-w-32"},
items=[
{ type: "item", label: "Documentation" },
{ type: "item", label: "Blog Posts" },
{ type: "item", label: "Changelog" }
]
) }}
</div>
</div>
{% endset %}
{{ code_preview("input-group-dropdown", code_dropdown, class="w-full max-w-sm") }}
<h3 id="example-group"><a href="#example-group">Group</a></h3>
{% set code_group %}
<div class="flex w-fit items-stretch">
<label for="url" class="text-sm flex items-center bg-muted border border-r-0 px-4 rounded-l-md">https://</label>
<div class="relative">
<input type="text" class="input pr-9 rounded-none">
<div class="absolute right-3 top-1/2 -translate-y-1/2 text-muted-foreground [&>svg]:size-4">{% lucide "link-2" %}</div>
</div>
<div class="text-sm flex items-center bg-muted border border-l-0 px-4 rounded-r-md">.com</div>
</div>
{% endset %}
{{ code_preview("input-group-group", code_group, class="w-full max-w-sm") }}
+9
View File
@@ -3,6 +3,8 @@ layout: layouts/page.njk
title: Item
description: A versatile component that you can use to display any content.
toc:
- label: Usage
id: usage
- label: Examples
id: examples
children:
@@ -58,6 +60,13 @@ toc:
{{ code_preview("item-basic", code_basic | prettyHtml, class="w-full max-w-md") }}
<h2 id="usage"><a href="#usage">Usage</a></h2>
<section class="prose">
<p>Items are pure HTML composition using Tailwind utility classes. Use semantic elements like <code>&lt;article&gt;</code> or <code>&lt;a&gt;</code>, apply flex layout, and compose with standard HTML elements like <code>&lt;h3&gt;</code>, <code>&lt;p&gt;</code>, <code>&lt;img&gt;</code>, or <code>&lt;button&gt;</code>.</p>
<p>Browse the examples below and copy the pattern that fits your needs.</p>
</section>
<h2 id="examples"><a href="#examples">Examples</a></h2>
<h3 id="example-variants"><a href="#example-variants">Variants</a></h3>