Skip to content

Commit 33147cd

Browse files
authored
Add contact form with HTML and CSS validation
> Artifact with no JavaScript at all that demonstrates form validation for a contact form > Add a note to the page that it’s a demo of HTML only validation and doesn’t submit anywhere > In fact add a view source summary/details at the bottom that, when opened, shows body.innerHTML in a pre tag - this can use JavaScript > Actually we want that to capture the html tag onwards not just body - and it needs to be below not next to the form https://claude.ai/share/0cdbbe99-fabe-42c2-8b6f-81085ba97518
1 parent e768143 commit 33147cd

File tree

1 file changed

+363
-0
lines changed

1 file changed

+363
-0
lines changed

html-validation-demo.html

Lines changed: 363 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,363 @@
1+
<!DOCTYPE html>
2+
3+
<html lang="en">
4+
<head>
5+
<meta charset="UTF-8">
6+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
7+
<title>Contact form</title>
8+
<style>
9+
* {
10+
box-sizing: border-box;
11+
margin: 0;
12+
padding: 0;
13+
}
14+
15+
body {
16+
font-family: Helvetica, Arial, sans-serif;
17+
background: #f0f2f5;
18+
min-height: 100vh;
19+
display: flex;
20+
flex-direction: column;
21+
align-items: center;
22+
justify-content: center;
23+
padding: 24px;
24+
color: #1a1a2e;
25+
}
26+
27+
.card {
28+
background: #fff;
29+
border-radius: 16px;
30+
box-shadow: 0 4px 24px rgba(0, 0, 0, 0.08);
31+
width: 100%;
32+
max-width: 480px;
33+
padding: 40px 36px 36px;
34+
}
35+
36+
.demo-banner {
37+
font-size: 13px;
38+
color: #92400e;
39+
background: #fef3c7;
40+
border: 1px solid #fde68a;
41+
border-radius: 8px;
42+
padding: 10px 14px;
43+
margin-bottom: 24px;
44+
line-height: 1.5;
45+
}
46+
47+
h1 {
48+
font-size: 24px;
49+
font-weight: 600;
50+
margin-bottom: 4px;
51+
}
52+
53+
.subtitle {
54+
font-size: 14px;
55+
color: #6b7280;
56+
margin-bottom: 32px;
57+
}
58+
59+
.field {
60+
position: relative;
61+
margin-bottom: 24px;
62+
}
63+
64+
label {
65+
display: block;
66+
font-size: 13px;
67+
font-weight: 600;
68+
color: #374151;
69+
margin-bottom: 6px;
70+
letter-spacing: 0.01em;
71+
}
72+
73+
.required-star {
74+
color: #ef4444;
75+
margin-left: 2px;
76+
}
77+
78+
input,
79+
textarea,
80+
select {
81+
font-family: Helvetica, Arial, sans-serif;
82+
font-size: 16px;
83+
width: 100%;
84+
padding: 10px 14px;
85+
border: 2px solid #d1d5db;
86+
border-radius: 10px;
87+
background: #fafafa;
88+
color: #1a1a2e;
89+
transition: border-color 0.2s, box-shadow 0.2s, background 0.2s;
90+
outline: none;
91+
}
92+
93+
textarea {
94+
resize: vertical;
95+
min-height: 100px;
96+
}
97+
98+
select {
99+
appearance: none;
100+
background-image: url(“data:image/svg+xml,%3Csvg xmlns=‘http://www.w3.org/2000/svg’ width=‘12’ height=‘8’%3E%3Cpath d=‘M1 1l5 5 5-5’ stroke=’%236b7280’ stroke-width=2’ fill=‘none’ stroke-linecap=‘round’/%3E%3C/svg%3E”);
101+
background-repeat: no-repeat;
102+
background-position: right 14px center;
103+
padding-right: 38px;
104+
cursor: pointer;
105+
}
106+
107+
input:focus,
108+
textarea:focus,
109+
select:focus {
110+
border-color: #6366f1;
111+
box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.15);
112+
background: #fff;
113+
}
114+
115+
/* Validation states — only show after user interaction via :not(:placeholder-shown) or :not(:focus) */
116+
input:not(:placeholder-shown):not(:focus):valid,
117+
textarea:not(:placeholder-shown):not(:focus):valid,
118+
select:valid:not(:focus) {
119+
border-color: #22c55e;
120+
background: #f0fdf4;
121+
}
122+
123+
input:not(:placeholder-shown):not(:focus):invalid,
124+
textarea:not(:placeholder-shown):not(:focus):invalid {
125+
border-color: #ef4444;
126+
background: #fef2f2;
127+
}
128+
129+
/* Validation icons */
130+
.field::after {
131+
position: absolute;
132+
right: 14px;
133+
font-size: 16px;
134+
pointer-events: none;
135+
opacity: 0;
136+
transition: opacity 0.2s;
137+
}
138+
139+
/* Checkmark for valid */
140+
.field:has(input:not(:placeholder-shown):not(:focus):valid)::after,
141+
.field:has(textarea:not(:placeholder-shown):not(:focus):valid)::after {
142+
content: “✓”;
143+
color: #22c55e;
144+
opacity: 1;
145+
bottom: 13px;
146+
}
147+
148+
/* X mark for invalid */
149+
.field:has(input:not(:placeholder-shown):not(:focus):invalid)::after,
150+
.field:has(textarea:not(:placeholder-shown):not(:focus):invalid)::after {
151+
content: “✗”;
152+
color: #ef4444;
153+
opacity: 1;
154+
bottom: 13px;
155+
}
156+
157+
/* For textarea, position the icon higher */
158+
.field:has(textarea)::after {
159+
top: 40px;
160+
bottom: auto;
161+
}
162+
163+
/* Hint text */
164+
.hint {
165+
display: block;
166+
font-size: 12px;
167+
color: #9ca3af;
168+
margin-top: 5px;
169+
transition: color 0.2s;
170+
}
171+
172+
.field:has(input:not(:placeholder-shown):not(:focus):invalid) .hint,
173+
.field:has(textarea:not(:placeholder-shown):not(:focus):invalid) .hint {
174+
color: #ef4444;
175+
}
176+
177+
/* Row layout for name fields */
178+
.row {
179+
display: flex;
180+
gap: 16px;
181+
}
182+
183+
.row .field {
184+
flex: 1;
185+
}
186+
187+
/* Submit button */
188+
button[type=“submit”] {
189+
font-family: Helvetica, Arial, sans-serif;
190+
font-size: 16px;
191+
font-weight: 600;
192+
width: 100%;
193+
padding: 13px 20px;
194+
border: none;
195+
border-radius: 10px;
196+
background: #6366f1;
197+
color: #fff;
198+
cursor: pointer;
199+
transition: background 0.2s, transform 0.1s, box-shadow 0.2s;
200+
margin-top: 8px;
201+
letter-spacing: 0.01em;
202+
}
203+
204+
button[type=“submit”]:hover {
205+
background: #4f46e5;
206+
box-shadow: 0 4px 12px rgba(99, 102, 241, 0.35);
207+
}
208+
209+
button[type=“submit”]:active {
210+
transform: scale(0.98);
211+
}
212+
213+
/* Footnote */
214+
.footnote {
215+
text-align: center;
216+
font-size: 12px;
217+
color: #9ca3af;
218+
margin-top: 16px;
219+
}
220+
221+
.footnote span {
222+
color: #ef4444;
223+
}
224+
225+
.view-source {
226+
width: 100%;
227+
max-width: 480px;
228+
margin-top: 16px;
229+
}
230+
231+
.view-source summary {
232+
font-size: 13px;
233+
font-weight: 600;
234+
color: #6b7280;
235+
cursor: pointer;
236+
padding: 8px 0;
237+
user-select: none;
238+
}
239+
240+
.view-source summary:hover {
241+
color: #374151;
242+
}
243+
244+
.view-source pre {
245+
background: #1e1e2e;
246+
color: #cdd6f4;
247+
font-family: “SF Mono”, Menlo, Consolas, monospace;
248+
font-size: 12px;
249+
line-height: 1.6;
250+
padding: 20px;
251+
border-radius: 12px;
252+
overflow-x: auto;
253+
white-space: pre-wrap;
254+
word-break: break-word;
255+
margin-top: 8px;
256+
}
257+
</style>
258+
259+
</head>
260+
<body>
261+
262+
<form class="card" action="#demo">
263+
<div class="demo-banner">
264+
<strong>Demo:</strong> This form showcases pure HTML &amp; CSS validation with no JavaScript. It does not submit anywhere.
265+
</div>
266+
267+
```
268+
<h1>Get in touch</h1>
269+
<p class="subtitle">We'd love to hear from you. Fill out the form below.</p>
270+
271+
<div class="row">
272+
<div class="field">
273+
<label for="first">First name<span class="required-star">*</span></label>
274+
<input
275+
type="text"
276+
id="first"
277+
placeholder=" "
278+
required
279+
minlength="2"
280+
pattern="[A-Za-zÀ-ÖØ-öø-ÿ\s\-']+"
281+
>
282+
<span class="hint">At least 2 characters, letters only</span>
283+
</div>
284+
<div class="field">
285+
<label for="last">Last name<span class="required-star">*</span></label>
286+
<input
287+
type="text"
288+
id="last"
289+
placeholder=" "
290+
required
291+
minlength="2"
292+
pattern="[A-Za-zÀ-ÖØ-öø-ÿ\s\-']+"
293+
>
294+
<span class="hint">At least 2 characters, letters only</span>
295+
</div>
296+
</div>
297+
298+
<div class="field">
299+
<label for="email">Email address<span class="required-star">*</span></label>
300+
<input
301+
type="email"
302+
id="email"
303+
placeholder=" "
304+
required
305+
>
306+
<span class="hint">Enter a valid email, e.g. you@example.com</span>
307+
</div>
308+
309+
<div class="field">
310+
<label for="phone">Phone number</label>
311+
<input
312+
type="tel"
313+
id="phone"
314+
placeholder=" "
315+
pattern="[\d\s\+\-\(\)]{7,20}"
316+
>
317+
<span class="hint">Optional — 7–20 digits, may include +, -, ( )</span>
318+
</div>
319+
320+
<div class="field">
321+
<label for="subject">Subject<span class="required-star">*</span></label>
322+
<select id="subject" required>
323+
<option value="" disabled selected>Choose a topic…</option>
324+
<option value="general">General inquiry</option>
325+
<option value="support">Support request</option>
326+
<option value="feedback">Feedback</option>
327+
<option value="partnership">Partnership</option>
328+
</select>
329+
</div>
330+
331+
<div class="field">
332+
<label for="message">Message<span class="required-star">*</span></label>
333+
<textarea
334+
id="message"
335+
placeholder=" "
336+
required
337+
minlength="10"
338+
maxlength="1000"
339+
></textarea>
340+
<span class="hint">Between 10 and 1,000 characters</span>
341+
</div>
342+
343+
<button type="submit">Send message</button>
344+
<p class="footnote"><span>*</span> Required fields</p>
345+
```
346+
347+
</form>
348+
349+
<details class="view-source">
350+
<summary>View source</summary>
351+
<pre><code id="source-output"></code></pre>
352+
</details>
353+
354+
<script type="module">
355+
const code = document.getElementById('source-output');
356+
const clone = document.documentElement.cloneNode(true);
357+
clone.querySelector('.view-source').remove();
358+
clone.querySelector('script').remove();
359+
code.textContent = '<!DOCTYPE html>\n' + clone.outerHTML.trim();
360+
</script>
361+
362+
</body>
363+
</html>

0 commit comments

Comments
 (0)