0
# Widgets and Rendering
1
2
HTML rendering system with customizable widgets for different input types, from basic text inputs to complex multi-select components and custom layouts. Widgets handle the conversion of form fields into HTML markup with proper attributes and styling.
3
4
## Capabilities
5
6
### Widget Utilities
7
8
Core utilities for HTML generation and attribute handling.
9
10
```python { .api }
11
def html_params(**kwargs) -> str:
12
"""
13
Generate HTML attributes from keyword arguments.
14
15
Parameters:
16
- **kwargs: Attribute name-value pairs
17
18
Returns:
19
str: HTML attribute string
20
21
Example:
22
html_params(id="myfield", class_="form-control", required=True)
23
# Returns: 'id="myfield" class="form-control" required'
24
"""
25
```
26
27
### Base Widget Classes
28
29
Foundation widget classes that other widgets inherit from.
30
31
```python { .api }
32
class Widget:
33
"""
34
Base widget class for HTML rendering.
35
"""
36
def __call__(self, field, **kwargs) -> str:
37
"""
38
Render field as HTML.
39
40
Parameters:
41
- field: Field instance to render
42
- **kwargs: Additional HTML attributes
43
44
Returns:
45
str: HTML markup
46
"""
47
48
class Input(Widget):
49
"""
50
Base class for HTML input elements.
51
52
Parameters:
53
- input_type: HTML input type attribute
54
"""
55
def __init__(self, input_type=None): ...
56
def __call__(self, field, **kwargs) -> str: ...
57
```
58
59
### Text Input Widgets
60
61
Widgets for various text input types.
62
63
```python { .api }
64
class TextInput(Input):
65
"""
66
Standard text input widget.
67
Renders: <input type="text" />
68
"""
69
def __init__(self): ...
70
71
class PasswordInput(Input):
72
"""
73
Password input widget (hides input value).
74
Renders: <input type="password" />
75
76
Parameters:
77
- hide_value: Whether to hide value in HTML (default: True)
78
"""
79
def __init__(self, hide_value=True): ...
80
81
class HiddenInput(Input):
82
"""
83
Hidden input widget.
84
Renders: <input type="hidden" />
85
"""
86
def __init__(self): ...
87
88
class TextArea(Widget):
89
"""
90
Multi-line textarea widget.
91
Renders: <textarea></textarea>
92
"""
93
def __call__(self, field, **kwargs) -> str: ...
94
```
95
96
### HTML5 Input Widgets
97
98
Modern HTML5 input types with enhanced validation and user experience.
99
100
```python { .api }
101
class EmailInput(Input):
102
"""
103
HTML5 email input widget.
104
Renders: <input type="email" />
105
"""
106
def __init__(self): ...
107
108
class URLInput(Input):
109
"""
110
HTML5 URL input widget.
111
Renders: <input type="url" />
112
"""
113
def __init__(self): ...
114
115
class SearchInput(Input):
116
"""
117
HTML5 search input widget.
118
Renders: <input type="search" />
119
"""
120
def __init__(self): ...
121
122
class TelInput(Input):
123
"""
124
HTML5 telephone input widget.
125
Renders: <input type="tel" />
126
"""
127
def __init__(self): ...
128
129
class NumberInput(Input):
130
"""
131
HTML5 number input widget.
132
Renders: <input type="number" />
133
134
Parameters:
135
- step: Step value for numeric input
136
- min: Minimum value
137
- max: Maximum value
138
"""
139
def __init__(self, step=None, min=None, max=None): ...
140
141
class RangeInput(Input):
142
"""
143
HTML5 range input widget.
144
Renders: <input type="range" />
145
146
Parameters:
147
- step: Step value for range input
148
"""
149
def __init__(self, step=None): ...
150
151
class ColorInput(Input):
152
"""
153
HTML5 color input widget.
154
Renders: <input type="color" />
155
"""
156
def __init__(self): ...
157
```
158
159
### Date and Time Widgets
160
161
Widgets for date and time input with HTML5 support.
162
163
```python { .api }
164
class DateInput(Input):
165
"""
166
HTML5 date input widget.
167
Renders: <input type="date" />
168
"""
169
def __init__(self): ...
170
171
class TimeInput(Input):
172
"""
173
HTML5 time input widget.
174
Renders: <input type="time" />
175
"""
176
def __init__(self): ...
177
178
class DateTimeInput(Input):
179
"""
180
HTML5 datetime input widget.
181
Renders: <input type="datetime" />
182
"""
183
def __init__(self): ...
184
185
class DateTimeLocalInput(Input):
186
"""
187
HTML5 datetime-local input widget.
188
Renders: <input type="datetime-local" />
189
"""
190
def __init__(self): ...
191
192
class MonthInput(Input):
193
"""
194
HTML5 month input widget.
195
Renders: <input type="month" />
196
"""
197
def __init__(self): ...
198
199
class WeekInput(Input):
200
"""
201
HTML5 week input widget.
202
Renders: <input type="week" />
203
"""
204
def __init__(self): ...
205
```
206
207
### Choice and Selection Widgets
208
209
Widgets for selecting from multiple options.
210
211
```python { .api }
212
class CheckboxInput(Input):
213
"""
214
Checkbox input widget.
215
Renders: <input type="checkbox" />
216
"""
217
def __init__(self): ...
218
219
class RadioInput(Input):
220
"""
221
Radio input widget.
222
Renders: <input type="radio" />
223
"""
224
def __init__(self): ...
225
226
class Select(Widget):
227
"""
228
Select dropdown widget.
229
Renders: <select><option>...</option></select>
230
231
Parameters:
232
- multiple: Whether to allow multiple selections
233
"""
234
def __init__(self, multiple=False): ...
235
def __call__(self, field, **kwargs) -> str: ...
236
237
class Option(Widget):
238
"""
239
Option element for Select widget.
240
Renders: <option value="...">label</option>
241
"""
242
def __call__(self, field, **kwargs) -> str: ...
243
```
244
245
### File Input Widgets
246
247
Widgets for file uploads.
248
249
```python { .api }
250
class FileInput(Input):
251
"""
252
File input widget.
253
Renders: <input type="file" />
254
255
Parameters:
256
- multiple: Whether to allow multiple file selection
257
"""
258
def __init__(self, multiple=False): ...
259
```
260
261
### Button and Submit Widgets
262
263
Widgets for form submission and actions.
264
265
```python { .api }
266
class SubmitInput(Input):
267
"""
268
Submit button widget.
269
Renders: <input type="submit" />
270
"""
271
def __init__(self): ...
272
```
273
274
### Container Widgets
275
276
Widgets for organizing and grouping multiple fields.
277
278
```python { .api }
279
class ListWidget(Widget):
280
"""
281
Container widget that renders fields as list items.
282
Used for RadioField and CheckboxField groups.
283
284
Parameters:
285
- html_tag: HTML tag for container (default: "ul")
286
- prefix_label: Whether to put label before input (default: True)
287
"""
288
def __init__(self, html_tag="ul", prefix_label=True): ...
289
def __call__(self, field, **kwargs) -> str: ...
290
291
class TableWidget(Widget):
292
"""
293
Container widget that renders fields in table format.
294
Used for FormField to organize nested forms.
295
296
Parameters:
297
- with_table_tag: Whether to wrap in <table> tag (default: True)
298
"""
299
def __init__(self, with_table_tag=True): ...
300
def __call__(self, field, **kwargs) -> str: ...
301
```
302
303
## Widget Usage Examples
304
305
### Basic Widget Usage
306
307
```python
308
from wtforms import Form, StringField
309
from wtforms.widgets import TextInput, TextArea
310
311
class MyForm(Form):
312
# Default widget (TextInput)
313
name = StringField('Name')
314
315
# Custom widget
316
bio = StringField('Biography', widget=TextArea())
317
318
# Widget with custom attributes
319
email = StringField('Email',
320
widget=TextInput(),
321
render_kw={'placeholder': 'Enter your email', 'class': 'form-control'}
322
)
323
324
# Render fields
325
form = MyForm()
326
print(form.name()) # <input id="name" name="name" type="text" value="" />
327
print(form.bio()) # <textarea id="bio" name="bio"></textarea>
328
print(form.email()) # <input class="form-control" id="email" name="email" placeholder="Enter your email" type="text" value="" />
329
```
330
331
### HTML5 Input Widgets
332
333
```python
334
from wtforms import Form, StringField, IntegerField, DateField
335
from wtforms.widgets import EmailInput, NumberInput, DateInput, ColorInput
336
337
class ProfileForm(Form):
338
email = StringField('Email', widget=EmailInput())
339
website = StringField('Website', widget=URLInput())
340
age = IntegerField('Age', widget=NumberInput(min=13, max=120))
341
birth_date = DateField('Birth Date', widget=DateInput())
342
favorite_color = StringField('Favorite Color', widget=ColorInput())
343
344
# Range input with custom attributes
345
satisfaction = IntegerField('Satisfaction',
346
widget=RangeInput(step=1),
347
render_kw={'min': 1, 'max': 10}
348
)
349
350
# Generated HTML includes HTML5 validation attributes
351
form = ProfileForm()
352
print(form.age()) # <input id="age" max="120" min="13" name="age" type="number" value="" />
353
```
354
355
### Select and Choice Widgets
356
357
```python
358
from wtforms import Form, SelectField, SelectMultipleField
359
from wtforms.widgets import Select
360
361
class PreferencesForm(Form):
362
# Default Select widget
363
country = SelectField('Country', choices=[
364
('us', 'United States'),
365
('ca', 'Canada')
366
])
367
368
# Multiple selection
369
languages = SelectMultipleField('Languages',
370
choices=[
371
('en', 'English'),
372
('es', 'Spanish'),
373
('fr', 'French')
374
],
375
widget=Select(multiple=True)
376
)
377
378
form = PreferencesForm()
379
print(form.country())
380
# <select id="country" name="country">
381
# <option value="us">United States</option>
382
# <option value="ca">Canada</option>
383
# </select>
384
385
print(form.languages())
386
# <select id="languages" multiple name="languages">
387
# <option value="en">English</option>
388
# <option value="es">Spanish</option>
389
# <option value="fr">French</option>
390
# </select>
391
```
392
393
### Radio Button Lists
394
395
```python
396
from wtforms import Form, RadioField
397
from wtforms.widgets import ListWidget, RadioInput
398
399
class SurveyForm(Form):
400
rating = RadioField('Rating',
401
choices=[
402
('1', 'Poor'),
403
('2', 'Fair'),
404
('3', 'Good'),
405
('4', 'Very Good'),
406
('5', 'Excellent')
407
],
408
widget=ListWidget(prefix_label=False),
409
option_widget=RadioInput()
410
)
411
412
form = SurveyForm()
413
print(form.rating())
414
# <ul id="rating">
415
# <li><input id="rating-0" name="rating" type="radio" value="1"> <label for="rating-0">Poor</label></li>
416
# <li><input id="rating-1" name="rating" type="radio" value="2"> <label for="rating-1">Fair</label></li>
417
# ...
418
# </ul>
419
```
420
421
### File Upload Widgets
422
423
```python
424
from wtforms import Form, FileField, MultipleFileField
425
from wtforms.widgets import FileInput
426
427
class UploadForm(Form):
428
single_file = FileField('Upload File', widget=FileInput())
429
multiple_files = MultipleFileField('Upload Files',
430
widget=FileInput(multiple=True)
431
)
432
433
form = UploadForm()
434
print(form.single_file()) # <input id="single_file" name="single_file" type="file" />
435
print(form.multiple_files()) # <input id="multiple_files" multiple name="multiple_files" type="file" />
436
```
437
438
### Custom Widget Development
439
440
```python
441
from wtforms.widgets import Widget
442
from wtforms import Form, StringField
443
444
class StarRatingWidget(Widget):
445
"""Custom star rating widget."""
446
447
def __call__(self, field, **kwargs):
448
kwargs.setdefault('id', field.id)
449
html = []
450
451
# Add star rating HTML
452
html.append(f'<div class="star-rating" id="{field.id}">')
453
for i in range(1, 6):
454
checked = 'checked' if field.data == str(i) else ''
455
html.append(f'''
456
<input type="radio" name="{field.name}" value="{i}" {checked} />
457
<label for="{field.id}-{i}">★</label>
458
''')
459
html.append('</div>')
460
461
return ''.join(html)
462
463
class ReviewForm(Form):
464
rating = StringField('Rating', widget=StarRatingWidget())
465
comment = StringField('Comment')
466
467
form = ReviewForm()
468
print(form.rating()) # Custom star rating HTML
469
```
470
471
### Widget with Bootstrap Classes
472
473
```python
474
from wtforms import Form, StringField, SelectField, SubmitField
475
from wtforms.widgets import TextInput, Select, SubmitInput
476
477
class BootstrapForm(Form):
478
name = StringField('Name',
479
widget=TextInput(),
480
render_kw={'class': 'form-control', 'placeholder': 'Enter your name'}
481
)
482
483
category = SelectField('Category',
484
choices=[('tech', 'Technology'), ('science', 'Science')],
485
widget=Select(),
486
render_kw={'class': 'form-select'}
487
)
488
489
submit = SubmitField('Submit',
490
widget=SubmitInput(),
491
render_kw={'class': 'btn btn-primary'}
492
)
493
494
form = BootstrapForm()
495
print(form.name())
496
# <input class="form-control" id="name" name="name" placeholder="Enter your name" type="text" value="" />
497
```
498
499
### Conditional Widget Selection
500
501
```python
502
from wtforms import Form, StringField
503
from wtforms.widgets import TextInput, TextArea
504
505
class DynamicForm(Form):
506
content = StringField('Content')
507
508
def __init__(self, use_textarea=False, *args, **kwargs):
509
super().__init__(*args, **kwargs)
510
511
# Choose widget based on condition
512
if use_textarea:
513
self.content.widget = TextArea()
514
else:
515
self.content.widget = TextInput()
516
517
# Usage
518
short_form = DynamicForm(use_textarea=False)
519
long_form = DynamicForm(use_textarea=True)
520
521
print(short_form.content()) # <input type="text" ... />
522
print(long_form.content()) # <textarea>...</textarea>
523
```
524
525
### Widget Attribute Manipulation
526
527
```python
528
from wtforms import Form, StringField
529
from wtforms.widgets import html_params
530
531
class CustomForm(Form):
532
username = StringField('Username')
533
534
# Render with custom attributes
535
form = CustomForm()
536
field = form.username
537
538
# Method 1: Using render_kw
539
field.render_kw = {'class': 'form-control', 'data-validate': 'true'}
540
print(field())
541
542
# Method 2: Passing attributes directly
543
print(field(class_='form-control', placeholder='Enter username'))
544
545
# Method 3: Using html_params utility
546
attrs = html_params(id=field.id, name=field.name, class_='form-control', required=True)
547
print(f'<input {attrs} />')
548
```
549
550
### Nested Form Widgets
551
552
```python
553
from wtforms import Form, StringField, FormField
554
from wtforms.widgets import TableWidget
555
556
class AddressForm(Form):
557
street = StringField('Street')
558
city = StringField('City')
559
zipcode = StringField('Zip Code')
560
561
class UserForm(Form):
562
name = StringField('Name')
563
address = FormField(AddressForm, widget=TableWidget())
564
565
form = UserForm()
566
print(form.address())
567
# <table>
568
# <tr><th><label for="address-street">Street</label></th>
569
# <td><input id="address-street" name="address-street" type="text" value="" /></td></tr>
570
# <tr><th><label for="address-city">City</label></th>
571
# <td><input id="address-city" name="address-city" type="text" value="" /></td></tr>
572
# ...
573
# </table>
574
```
575
576
### Widget Theming and Styling
577
578
```python
579
from wtforms import Form, StringField, SelectField, BooleanField
580
from wtforms.widgets import CheckboxInput
581
582
class ThemedForm(Form):
583
# Material Design theme
584
name = StringField('Name', render_kw={
585
'class': 'mdc-text-field__input',
586
'placeholder': ' ' # Required for Material Design
587
})
588
589
# Tailwind CSS theme
590
category = SelectField('Category',
591
choices=[('a', 'Option A'), ('b', 'Option B')],
592
render_kw={
593
'class': 'block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500'
594
}
595
)
596
597
# Custom checkbox styling
598
agree = BooleanField('I agree',
599
widget=CheckboxInput(),
600
render_kw={
601
'class': 'h-4 w-4 text-indigo-600 focus:ring-indigo-500 border-gray-300 rounded'
602
}
603
)
604
605
form = ThemedForm()
606
# Each field renders with appropriate CSS classes
607
```