Community Wolf
Community
APIAboutContact
Book a demo

Community Wolf

Real-time safety intelligence for citizens and the teams who protect them.

Community Tools

  • Public map
  • Namola
  • WhatsApp

Business Tools

  • Access Control
  • Intelligence
  • Safety API
  • Group Agents
  • Patrol Management
  • Roster Management
  • Safety Survey
  • Pricing
  • Pricing calculator

Resources

  • Docs
  • Changelog
  • Design system

Company

  • Community
  • Business
  • API
  • About
  • Contact

Get pilot updates

New features, city launches, and integration partnerships - delivered monthly.

Subscribe to updates

(c) 2026 Community Wolf. All rights reserved.

Privacy PolicyTerms of Service
Wolf design system
Foundations
  • Brand
  • Voice and tone
  • Colour
  • Typography
  • Spacing and layout
  • Radius, elevation and borders
  • Motion
  • Iconography
  • Imagery and photography
  • Data visualisation
  • Accessibility
Patterns
  • App shell
  • Hero sections
  • Empty states
  • Error states
  • Skeletons and loading
  • Forms
  • Tables and data density
  • Confirmation and destructive actions
  • Authentication surfaces
Components
  • Button
  • Input
  • Card
  • Badge
  • Dialog
  • Label
  • Textarea
  • Select
  • Checkbox
  • Radio group
  • Switch
  • Form field
  • Tabs
  • Popover
  • Tooltip
  • Accordion
  • Alert dialog
  • Confirm dialog
  • Toast (Sonner)
  • Skeleton
  • Empty state

patterns

Forms

react-hook-form state, zod schemas, shadcn Form primitives. Label above input, error below, verb in the submit button — never 'Submit.'

Read the full spec

Live specimen — report intake

Blur-level validation, specific error messages, submit button reflects async state. Try submitting with blank fields to see the rhythm.

We'll reply from wolfbrain@agentmail.to.

tsx
const reportIntakeSchema = z.object({
  name: z.string().min(2, "Name must be at least 2 characters."),
  email: z.string().email("That doesn't look like a valid email address."),
  area: z.string().min(2, "Enter the area or neighbourhood name."),
  details: z.string().min(10, "Give us at least a sentence or two of detail."),
});

const form = useForm<ReportIntakeValues>({
  resolver: zodResolver(reportIntakeSchema),
  defaultValues: { name: "", email: "", area: "", details: "" },
});

<Form {...form}>
  <form onSubmit={form.handleSubmit(onSubmit)} className="space-y-6">
    <FormField
      control={form.control}
      name="email"
      render={({ field }) => (
        <FormItem>
          <FormLabel>Email</FormLabel>
          <FormControl>
            <Input type="email" autoComplete="email" {...field} />
          </FormControl>
          <FormDescription>We'll reply from wolfbrain@agentmail.to.</FormDescription>
          <FormMessage />
        </FormItem>
      )}
    />
    {/* …more fields… */}
  </form>
</Form>

Anatomy — four slots per field

Every FormField renders a subset of these. Label and control are mandatory; description and error are contextual.

  • FormLabelAlways visible · sentence case · no trailing colon
  • FormControlInput / Textarea / Select — wires aria-* automatically
  • FormDescriptionOptional helper · muted · above the error
  • FormMessageRenders only on error · red · specific

Validation rhythm

Don't validate every keystroke. Wait for blur, then stay live only for the field the user already errored on.

  • On blurFirst pass — validate the field the user just left.
  • On submitRe-validate everything, surface a summary for ≥5 fields.
  • On change (errored fields only)Live correction — lets the user see the error resolve.

Copy — specific, not sterile

Per voice-and-tone §5.3 and §4.4 — say what's wrong, not just that something is.

  • Avoid

    Invalid email

    Good

    That doesn't look like a valid email address.

  • Avoid

    Required

    Good

    Email is required.

  • Avoid

    Too short

    Good

    Password must be at least 8 characters.

  • Avoid

    Submit

    Good

    Send report / Save changes / Sign in

Previous — PatternsSkeletons and loading
Next — PatternsTables and data density