nette-forms
Invoke before creating or modifying Nette Forms. Provides form controls, validation, rendering patterns. Use when working with form factories, form controls, validation rules, form events, rendering forms in Latte, Bootstrap integration, form containers, or form error handling. Also trigger for $form/$data in Nette context.
What this skill does
## Nette Forms
Nette Forms provides secure, reusable forms with automatic validation on both client and server side.
```shell
composer require nette/forms
```
### Forms in Presenters
Forms are created in factory methods named `createComponent<Name>`:
```php
protected function createComponentRegistrationForm(): Form
{
$form = new Form;
$form->addText('name', 'Name:')
->setRequired('Please enter your name.');
$form->addEmail('email', 'Email:')
->setRequired('Please enter your email.');
$form->addPassword('password', 'Password:')
->setRequired('Please enter password.')
->addRule($form::MinLength, 'Password must be at least %d characters', 8);
$form->addSubmit('send', 'Register');
$form->onSuccess[] = $this->registrationFormSucceeded(...);
return $form;
}
private function registrationFormSucceeded(Form $form, \stdClass $data): void
{
// $data->name, $data->email, $data->password
$this->flashMessage('Registration successful.');
$this->redirect('Home:');
}
```
Render in template:
```latte
{control registrationForm}
```
### Create and Edit Pattern
Unified form for both creating and editing records:
```php
class ProductPresenter extends BasePresenter
{
public function __construct(
private ProductFacade $facade,
) {}
public function actionEdit(int $id = null): void
{
if ($id) {
$product = $this->facade->getProduct($id);
$this->template->product = $product;
$this['productForm']->setDefaults($product->toArray());
}
}
protected function createComponentProductForm(): Form
{
$form = new Form;
$form->addText('name', 'Name:')
->setRequired();
$form->addTextArea('description', 'Description:');
$form->addInteger('price', 'Price:')
->setRequired()
->addRule($form::Min, 'Price must be positive', 1);
$form->addSubmit('send', 'Save');
$form->onSuccess[] = $this->productFormSucceeded(...);
return $form;
}
private function productFormSucceeded(Form $form, \stdClass $data): void
{
$id = $this->getParameter('id');
if ($id) {
$this->facade->update($id, (array) $data);
$this->flashMessage('Product updated.');
} else {
$this->facade->create((array) $data);
$this->flashMessage('Product created.');
}
$this->redirect('default');
}
}
```
Template:
```latte
{block content}
<h1>{if $product}Edit: {$product->name}{else}New Product{/if}</h1>
{form productForm}
<table>
<tr><td>{label name}{input name}</td></tr>
<tr><td>{label description}{input description}</td></tr>
<tr><td>{label price}{input price}</td></tr>
</table>
{input send}
{/form}
{/block}
```
Defaults are set in `actionEdit()` via `$form->setDefaults()` – never set defaults in the template.
### Form Reuse with Factory
A common base `FormFactory` creates `Form` instances with shared configuration (CSRF, renderer, translation). Changing it in one place affects all forms in the application – no need to hunt through individual presenters:
```php
final class FormFactory
{
public function create(): Form
{
$form = new Form;
// Shared setup for all forms: renderer, translator, default classes, etc.
return $form;
}
}
```
For specific forms used in multiple places, create a dedicated factory:
```php
final class ProductFormFactory
{
public function __construct(
private FormFactory $formFactory,
) {}
public function create(callable $onSuccess): Form
{
$form = $this->formFactory->create();
$form->addText('name', 'Name:')
->setRequired();
$form->addTextArea('description', 'Description:');
$form->addInteger('price', 'Price:')
->setRequired();
$form->addSubmit('send');
$form->onSuccess[] = function (Form $form, \stdClass $data) use ($onSuccess) {
$onSuccess($data);
};
return $form;
}
}
```
Use in presenter:
```php
public function __construct(
private ProductFormFactory $productFormFactory,
) {}
protected function createComponentProductForm(): Form
{
return $this->productFormFactory->create(
function (\stdClass $data): void {
$this->facade->save($data);
$this->redirect('default');
},
);
}
```
### Common Form Controls
| Method | Creates |
|--------|---------|
| `addText($name, $label)` | Text input |
| `addPassword($name, $label)` | Password input |
| `addTextArea($name, $label)` | Multi-line text |
| `addEmail($name, $label)` | Email with validation |
| `addInteger($name, $label)` | Integer input |
| `addFloat($name, $label)` | Decimal input |
| `addCheckbox($name, $caption)` | Checkbox |
| `addCheckboxList($name, $label, $items)` | Multiple checkboxes |
| `addRadioList($name, $label, $items)` | Radio buttons |
| `addSelect($name, $label, $items)` | Dropdown |
| `addMultiSelect($name, $label, $items)` | Multi-select |
| `addUpload($name, $label)` | File upload |
| `addMultiUpload($name, $label)` | Multiple files |
| `addDate($name, $label)` | Date picker |
| `addTime($name, $label)` | Time picker |
| `addDateTime($name, $label)` | Combined date+time picker |
| `addHidden($name)` | Hidden field |
| `addSubmit($name, $caption)` | Submit button |
| `addButton($name, $caption)` | Button |
See [the complete control reference](references/controls.md) for all form controls.
### Basic Validation
```php
$form->addText('name')
->setRequired('Name is required.')
->addRule($form::MinLength, 'At least %d characters', 3)
->addRule($form::MaxLength, 'Maximum %d characters', 100);
$form->addEmail('email')
->setRequired()
->addRule($form::Email, 'Invalid email format.');
$form->addInteger('age')
->addRule($form::Range, 'Age must be between %d and %d', [18, 120]);
$form->addPassword('password')
->setRequired()
->addRule($form::MinLength, 'At least %d characters', 8);
$form->addPassword('password2')
->setRequired()
->addRule($form::Equal, 'Passwords must match', $form['password']);
```
See [the complete validation reference](references/validation.md) for all rules and conditions.
### Conditional Validation
```php
$form->addCheckbox('newsletter', 'Subscribe to newsletter');
$form->addEmail('email')
->addConditionOn($form['newsletter'], $form::Equal, true)
->setRequired('Email required for newsletter.');
```
### Form Rendering
**Default rendering:**
```latte
{control productForm}
```
**Manual rendering:**
```latte
{form productForm}
<table>
<tr>
<th>{label name /}</th>
<td>{input name} {inputError name}</td>
</tr>
<tr>
<th>{label email /}</th>
<td>{input email} {inputError email}</td>
</tr>
</table>
{input send}
{/form}
```
**With Bootstrap:**
```latte
{form productForm class => 'form-horizontal'}
<div class="mb-3">
{label name class => 'form-label' /}
{input name class => 'form-control'}
{inputError name class => 'invalid-feedback'}
</div>
{/form}
```
See [the complete rendering reference](references/rendering.md) for all rendering options.
### Form Events
```php
// Before rendering (modify form)
$form->onRender[] = function (Form $form): void {
// Add CSS classes, modify controls
};
// After successful validation
$form->onSuccess[] = function (Form $form, \stdClass $data): void {
// Process valid data
};
// After any submission (valid or invalid)
$form->onSubmit[] = function (Form $form): void {
// Logging, analytics
};
// Custom validation after rules pass
$form->onValidate[] = function (Form $form): void {
if ($this->isBlocked($form->getValues()->email)) {
$form->addError('This email is blocked.');
}
};
```
### Error Handling
```php
private function productFormSucceeded(Form $form, \stdClass $data): void
{
try {
$this->facade->save($data);
$this->redirect('default');
} catch (DuplicateEntryException) {
$form['email']->addError('Email already exists.');
} catch (\Exception $e) {
$form->addError('An error occurred.');
}
}
```
### Anti-Patterns to Avoid
- **Don't put business logic in form handlers** – use services/facades. Form handlers should only coordinate (call service, flash message, redirect), not implement business rules.
- **Don't create forms in action methods** – use `createComponent*` factory. NeRelated in General
modeling-omnistudio-epc-catalog
IncludedSalesforce Industries CME EPC product-modeling skill for Product2-based catalog creation. Use when creating EPC products, configuring product attributes, building offer bundles with Product Child Items, or reviewing EPC DataPack JSON metadata for product catalog changes. TRIGGER when: user creates or updates Product2 EPC records, AttributeAssignment payloads, AttributeMetadata/AttributeDefaultValues, Offer bundles, or ProductChildItem relationships. DO NOT TRIGGER when: designing OmniScripts/FlexCards/Integration Procedures (use building-omnistudio-omniscript, building-omnistudio-flexcard, or building-omnistudio-integration-procedure), implementing Apex business logic (use generating-apex), or troubleshooting deployment pipelines (use deploying-metadata).
relationship-science-coach
IncludedUse this skill for direct, practical adult relationship coaching: couples conflict, repair, trust, marriage, dating, flirting, attachment patterns, emotional connection, sex, desire differences, eroticism, kink negotiation, affection, love languages, breakups, and long-term passion. Draw on Gottman, EFT and Hold Me Tight, attachment science, modern sex research, Perel, Nagoski, Kerner, Schnarch, Love and Stosny, and flexible love-language tools. Be concrete and low-hedge. Redirect only for imminent danger, abuse, coercive control, minors, non-consent, self-harm, stalking, or medical/legal/psychiatric decisions.
building-sf-integrations
IncludedSalesforce integration architecture and runtime plumbing with 120-point scoring. Use this skill to set up Named Credentials, External Credentials, External Services, REST/SOAP callout patterns, Platform Events, and Change Data Capture. TRIGGER when: user sets up Named Credentials, External Services, REST/SOAP callouts, Platform Events, CDC, or touches .namedCredential-meta.xml files. DO NOT TRIGGER when: Connected App/OAuth config (use configuring-connected-apps), Apex-only logic (use generating-apex), or data import/export (use handling-sf-data).
venue-templates
IncludedAccess comprehensive LaTeX templates, formatting requirements, and submission guidelines for major scientific publication venues (Nature, Science, PLOS, IEEE, ACM), academic conferences (NeurIPS, ICML, CVPR, CHI), research posters, and grant proposals (NSF, NIH, DOE, DARPA). This skill should be used when preparing manuscripts for journal submission, conference papers, research posters, or grant proposals and need venue-specific formatting requirements and templates.
let-fate-decide
IncludedDraws the 12 Houses of the Zodiac Tarot spread to inject entropy into planning when prompts are vague, ambiguous, or casually delegated. Interprets the spread to guide next steps. Use when the user says 'let fate decide', 'YOLO', 'whatever', 'idk', or other nonchalant phrases, makes Yu-Gi-Oh references, or when you are about to arbitrarily pick between multiple reasonable approaches. Prefer over ask-questions-if-underspecified when the user's tone is casual or playful rather than precision-seeking.
net-ops
IncludedCross-platform network troubleshooting (Windows, macOS, Linux) via local or remote shell. Use for: DNS broken, can't resolve hostnames, nslookup/dig works but apps fail, NRPT, WFP, scutil, /etc/resolver, systemd-resolved, /etc/resolv.conf, NetworkManager, VPN DNS leak residue (ProtonVPN/Mullvad/WireGuard/AnyConnect), AV/firewall blocking DNS or DoH, Tailscale DNS interaction, intermittent connectivity, remote diagnostics over SSH.