Skip to content

ModeratedTextarea ​

A textarea component with real-time content moderation and visual feedback.

Overview ​

The ModeratedTextarea component extends the standard HTML textarea with built-in content moderation. As users type, it automatically checks the content against your moderation policy and provides instant visual feedback.

Features ​

  • ✅ Real-time moderation - Content checked as users type
  • ✅ Visual feedback - Color-coded borders (green/yellow/red)
  • ✅ Debounced API calls - Configurable delay to reduce costs
  • ✅ Customizable - Override feedback, styling, and behavior
  • ✅ Block unsafe content - Optionally prevent submission of flagged content
  • ✅ Fully typed - Complete TypeScript support

Quick Start ​

tsx
import { ModeratedTextarea } from '@vettly/react'
import '@vettly/react/styles.css'

function CommentForm() {
  return (
    <ModeratedTextarea
      apiKey="your-api-key"
      policyId="moderate"
      placeholder="Write a comment..."
    />
  )
}

Props ​

Required Props ​

PropTypeDescription
apiKeystringYour Vettly API key
policyIdstringPolicy ID to use for moderation

Optional Props ​

PropTypeDefaultDescription
valuestringundefinedControlled component value
onChange(value: string, result: ModerationResult) => voidundefinedCallback when value changes
debounceMsnumber500Delay before checking content (ms)
showFeedbackbooleantrueShow moderation status messages
blockUnsafebooleanfalsePrevent typing when content is unsafe
customFeedback(result: ModerationResult) => ReactNodeundefinedCustom feedback component
onModerationResult(result: CheckResponse) => voidundefinedCallback with full moderation response
onModerationError(error: Error) => voidundefinedError handling callback
classNamestringundefinedAdditional CSS classes
placeholderstringundefinedTextarea placeholder text
rowsnumberundefinedNumber of visible text rows
disabledbooleanfalseDisable the textarea

All standard HTML textarea attributes are also supported.

Examples ​

Basic Usage ​

tsx
import { ModeratedTextarea } from '@vettly/react'
import '@vettly/react/styles.css'

function App() {
  return (
    <ModeratedTextarea
      apiKey="vettly_xxxxx"
      policyId="moderate"
      placeholder="Type something..."
    />
  )
}

Controlled Component ​

tsx
import { useState } from 'react'
import { ModeratedTextarea } from '@vettly/react'

function CommentForm() {
  const [comment, setComment] = useState('')
  const [isSafe, setIsSafe] = useState(true)

  return (
    <div>
      <ModeratedTextarea
        apiKey={import.meta.env.VITE_VETTLY_API_KEY}
        policyId="social_media"
        value={comment}
        onChange={(value, result) => {
          setComment(value)
          setIsSafe(result.safe)
        }}
        placeholder="Write a comment..."
        rows={4}
      />

      <button disabled={!isSafe}>
        Submit Comment
      </button>
    </div>
  )
}

Block Unsafe Content ​

Prevent users from typing content that violates your policy:

tsx
<ModeratedTextarea
  apiKey="vettly_xxxxx"
  policyId="strict"
  blockUnsafe={true}
  placeholder="Only safe content allowed..."
/>

When blockUnsafe is enabled, the textarea becomes read-only when unsafe content is detected.

Custom Debounce ​

Adjust how quickly content is checked (reduce API calls):

tsx
<ModeratedTextarea
  apiKey="vettly_xxxxx"
  policyId="moderate"
  debounceMs={1000} // Wait 1 second after user stops typing
/>

Custom Feedback ​

Override the default feedback message:

tsx
<ModeratedTextarea
  apiKey="vettly_xxxxx"
  policyId="moderate"
  customFeedback={(result) => {
    if (result.isChecking) {
      return <p>🔍 Checking your content...</p>
    }

    if (result.error) {
      return <p className="error">❌ {result.error}</p>
    }

    if (result.flagged) {
      const triggered = result.categories.filter(c => c.triggered)
      return (
        <div className="warning">
          ⚠️ Content flagged for: {triggered.map(c => c.category).join(', ')}
        </div>
      )
    }

    if (result.safe) {
      return <p className="success">✅ Content looks good!</p>
    }

    return null
  }}
/>

Hide Feedback ​

Disable the feedback UI entirely:

tsx
<ModeratedTextarea
  apiKey="vettly_xxxxx"
  policyId="moderate"
  showFeedback={false}
  onModerationResult={(result) => {
    // Handle moderation result yourself
    console.log('Moderation result:', result)
  }}
/>

Custom Styling ​

Override the default styles:

tsx
<ModeratedTextarea
  apiKey="vettly_xxxxx"
  policyId="moderate"
  className="my-custom-textarea"
  style={{
    minHeight: '200px',
    fontSize: '16px',
    padding: '12px'
  }}
/>

Custom CSS:

css
.my-custom-textarea {
  border-radius: 8px;
  font-family: 'Inter', sans-serif;
}

/* Override feedback colors */
.my-custom-textarea[data-moderation-status="safe"] {
  border-color: #10b981;
}

.my-custom-textarea[data-moderation-status="flagged"] {
  border-color: #f59e0b;
}

.my-custom-textarea[data-moderation-status="blocked"] {
  border-color: #ef4444;
}

Error Handling ​

Handle moderation errors gracefully:

tsx
import { useState } from 'react'

function App() {
  const [error, setError] = useState<string | null>(null)

  return (
    <div>
      <ModeratedTextarea
        apiKey="vettly_xxxxx"
        policyId="moderate"
        onModerationError={(err) => {
          console.error('Moderation failed:', err)
          setError(err.message)
        }}
      />

      {error && (
        <div className="error-banner">
          Failed to check content: {error}
        </div>
      )}
    </div>
  )
}

Full Moderation Response ​

Access the complete moderation data:

tsx
<ModeratedTextarea
  apiKey="vettly_xxxxx"
  policyId="moderate"
  onModerationResult={(result) => {
    console.log('Decision ID:', result.decisionId)
    console.log('Safe:', result.safe)
    console.log('Action:', result.action)
    console.log('Categories:', result.categories)
    console.log('Provider:', result.provider)
    console.log('Latency:', result.latency)
    console.log('Cost:', result.cost)
  }}
/>

Visual States ​

The component automatically updates its appearance based on moderation results:

StateBorder ColorDescription
DefaultGrayNo content or checking not started
CheckingBlueContent is being checked
SafeGreenContent passed moderation
WarningYellowContent flagged but not blocked
BlockedRedContent violates policy
ErrorRedModeration check failed

Default CSS Classes ​

The component uses these CSS classes which you can override:

  • .vettly-textarea - Main textarea element
  • .vettly-textarea-wrapper - Container div
  • .vettly-feedback - Feedback message container
  • .vettly-feedback-safe - Safe content message
  • .vettly-feedback-warning - Warning message
  • .vettly-feedback-error - Error message

Performance Tips ​

  1. Increase debounce for long-form content:

    tsx
    debounceMs={1500} // For blog posts, articles
  2. Use blockUnsafe sparingly:

    • Can be frustrating for users
    • Better to show warnings and let them edit
  3. Monitor costs:

    tsx
    onModerationResult={(result) => {
      // Log API costs in production
      console.log('API cost:', result.cost)
    }}

Accessibility ​

The component includes:

  • Proper ARIA attributes
  • Keyboard navigation support
  • Screen reader announcements for moderation status
  • Focus management

TypeScript ​

Full TypeScript support with exported types:

tsx
import type {
  ModeratedTextareaProps,
  ModerationResult
} from '@vettly/react'

const props: ModeratedTextareaProps = {
  apiKey: 'vettly_xxxxx',
  policyId: 'moderate',
  onChange: (value: string, result: ModerationResult) => {
    // Fully typed!
  }
}

See Also ​