Input Components Demo

This page demo component based input fields.

Source Code of demo-inputs.tsx
(import statements omitted for simplicity, click to expand)
import { Style } from '../components/style.js'
import { o } from '../jsx/jsx.js'
import debug from 'debug'
import { title } from '../../config.js'
import { Routes } from '../routes.js'
import Menu from '../components/menu.js'
import { Switch } from '../components/router.js'
import DemoSelect from './demo-inputs/demo-select.js'
import DemoSingleFieldForm from './demo-inputs/demo-single-field-form.js'
import DemoComboBox from './demo-inputs/demo-combo-box.js'
import SourceCode from '../components/source-code.js'
const log = debug('demo-single-field-form.tsx')
log.enabled = true

let style = Style(/* css */ `
#demo-inputs .code-demo {
  display: flex;
  flex-wrap: wrap;
}
`)

let content = (
  <div id="demo-inputs">
    <h1>Input Components Demo</h1>
    <p>This page demo component based input fields.</p>

    {style}
    <link rel="stylesheet" href="/lib/prism/prism.css" />
    <script src="/lib/prism/prism.js"></script>

    <SourceCode page="demo-inputs.tsx" />

    <p>
      The example code snippets below are simplified for illustration. You can
      click "Source Code of [page]" to see the complete source code.
    </p>

    <fieldset>
      <legend>
        <Menu
          routes={[
            { url: '/inputs/select', menuText: '<Select/>' },
            { url: '/inputs/combo-box', menuText: '<ComboBox/>' },
            {
              url: '/inputs/single-field-form',
              menuText: 'newSingleFieldForm()',
            },
          ]}
          separator=" | "
        />
      </legend>
      {Switch({
        '/inputs': DemoSelect,
        '/inputs/select': DemoSelect,
        '/inputs/combo-box': DemoComboBox,
        '/inputs/single-field-form': DemoSingleFieldForm.content,
      })}
    </fieldset>
  </div>
)

let routes = {
  '/inputs': {
    title: title('Demo input components'),
    description: 'Demonstrate component-based input fields',
    menuText: 'Inputs',
    menuMatchPrefix: true,
    node: content,
  },
  '/inputs/select': {
    title: title('Demo <Select/> components'),
    description:
      'Demonstrate building select and option elements with <Select/>',
    node: content,
  },
  '/inputs/combo-box': {
    title: title('Demo <ComboBox/> component'),
    description:
      'Demonstrate searchable dropdown component with single or multiple selection',
    node: content,
  },
  '/inputs/single-field-form': {
    title: title('Demo single-field-form component creator'),
    description: 'Demonstrate per-field saving with realtime update',
    node: content,
  },
  ...DemoSingleFieldForm.routes,
} satisfies Routes

export default { routes }

The example code snippets below are simplified for illustration. You can click "Source Code of [page]" to see the complete source code.

<ComboBox/> is a searchable dropdown component with single or multiple selection support. It provides a better user experience than native <select> for long lists of options.

Basic Single Select

By default, <ComboBox/> allows single selection. Use auto-set to automatically set the input value when an option is selected.

Example Source Code<ComboBox placeholder="Select a fruit..." options={[ { value: 'apple', label: 'Apple' }, { value: 'banana', label: 'Banana' }, { value: 'cherry', label: 'Cherry' }, ]} auto-set="label" onchange="console.log('Selected:', event.detail.value)" />
Preview
Apple
Banana
Cherry
Date
Elderberry
Selected value: None

Multiple Selection

Set multiple attribute to allow selecting multiple options. The event.detail.value will be an array of selected values. You can access the selected values programmatically using the value property on the combo-box element.

Example Source Code<ComboBox id="demoComboBox" placeholder="Select multiple colors..." options={[ { value: 'red', label: 'Red' }, { value: 'green', label: 'Green' }, { value: 'blue', label: 'Blue' }, ]} multiple auto-set="label" onchange="console.log('Selected values:', event.detail.value)" /> <button onclick="console.log('Selected values:', demoComboBox.value)"> Log Selected Values </button>
Preview
Red
Green
Blue
Yellow
Purple
Selected values: None

Search Behavior

By default, search matches against the label, not the value. You can provide custom search text using the search property to match alternative names or aliases.

Example: With search: "US USA United States America", searching for any of these terms will match: "US", "USA", "United States", or "America".

Example Source Code<ComboBox placeholder="Search countries..." options={[ // search property allows matching multiple terms // Try searching: "US", "USA", "United States", or "America" - all will match { value: 'us', label: 'United States', search: 'US USA United States America' }, { value: 'uk', label: 'United Kingdom', search: 'UK Britain United Kingdom' }, { value: 'jp', label: 'Japan', search: 'JP Japan Nihon' }, ]} auto-set="label" onchange="console.log('Selected:', event.detail.value)" />
Preview
United States
United Kingdom
Japan
South Korea
China
Selected value: None

Case Sensitive Search

By default, search is case-insensitive. Set case-sensitive attribute to enable case-sensitive matching.

Example Source Code<ComboBox placeholder="Case sensitive search..." options={[ { value: 'js', label: 'JavaScript' }, { value: 'ts', label: 'TypeScript' }, { value: 'py', label: 'Python' }, ]} case-sensitive auto-set="label" onchange="console.log('Selected:', event.detail.value)" />
Preview
JavaScript
TypeScript
Python
Go
Rust
Selected value: None

Auto-Set Value

Use auto-set with "value" to set the input's value to the option's value instead of the label. This is useful when you need to submit the form with the actual value.

Example Source Code<form id="demoForm"> <ComboBox name="language" placeholder="Select programming language..." options={[ { value: 'javascript', label: 'JavaScript' }, { value: 'typescript', label: 'TypeScript' }, { value: 'python', label: 'Python' }, ]} auto-set="value" onchange="console.log('Form value:', demoForm.language.value)" /> </form>
Preview
JavaScript
TypeScript
Python
Java
Rust
Form value: None
Source Code of demo-inputs/demo-combo-box.tsx
(import statements omitted for simplicity, click to expand)
import { ComboBox } from '../../components/combo-box.js'
import code from '../../components/inline-code.js'
import { Script } from '../../components/script.js'
import SourceCode from '../../components/source-code.js'
import { o } from '../../jsx/jsx.js'
let comboBox = code('<ComboBox/>')
let placeholder = code('placeholder')
let options = code('options')
let value = code('value')
let multiple = code('multiple')
let autoSet = code('auto-set')
let caseSensitive = code('case-sensitive')

let content = (
  <>
    <p>
      {comboBox} is a searchable dropdown component with single or multiple
      selection support. It provides a better user experience than native{' '}
      {code('<select>')} for long lists of options.
    </p>

    <h2>Basic Single Select</h2>
    <p>
      By default, {comboBox} allows single selection. Use {autoSet} to
      automatically set the input value when an option is selected.
    </p>
    <div class="code-demo">
      <fieldset>
        <legend>Example Source Code</legend>
        <code class="language-tsx" style="padding: 0.5rem">
          {
            /* html */ `
<ComboBox
  placeholder="Select a fruit..."
  options={[
    { value: 'apple', label: 'Apple' },
    { value: 'banana', label: 'Banana' },
    { value: 'cherry', label: 'Cherry' },
  ]}
  auto-set="label"
  onchange="console.log('Selected:', event.detail.value)"
/>
`.trim()
          }
        </code>
      </fieldset>
      <fieldset>
        <legend>Preview</legend>
        <ComboBox
          id="demoComboSingle"
          placeholder="Select a fruit..."
          options={[
            { value: 'apple', label: 'Apple' },
            { value: 'banana', label: 'Banana' },
            { value: 'cherry', label: 'Cherry' },
            { value: 'date', label: 'Date' },
            { value: 'elderberry', label: 'Elderberry' },
          ]}
          auto-set="label"
          onchange="handleComboChange('demoComboSingle', 'demoComboSingleValue', event)"
        />
        <div style="margin-top: 0.5rem;">
          <strong>Selected value:</strong>{' '}
          <span id="demoComboSingleValue" style="color: #0054e9;">
            None
          </span>
        </div>
      </fieldset>
    </div>

    <h2>Multiple Selection</h2>
    <p>
      Set {multiple} attribute to allow selecting multiple options. The{' '}
      {code('event.detail.value')} will be an array of selected values. You can
      access the selected values programmatically using the {code('value')}{' '}
      property on the combo-box element.
    </p>
    <div class="code-demo">
      <fieldset>
        <legend>Example Source Code</legend>
        <code class="language-tsx" style="padding: 0.5rem">
          {
            /* html */ `
<ComboBox
  id="demoComboBox"
  placeholder="Select multiple colors..."
  options={[
    { value: 'red', label: 'Red' },
    { value: 'green', label: 'Green' },
    { value: 'blue', label: 'Blue' },
  ]}
  multiple
  auto-set="label"
  onchange="console.log('Selected values:', event.detail.value)"
/>
<button onclick="console.log('Selected values:', demoComboBox.value)">
  Log Selected Values
</button>
`.trim()
          }
        </code>
      </fieldset>
      <fieldset>
        <legend>Preview</legend>
        <ComboBox
          id="demoComboMultiple"
          placeholder="Select multiple colors..."
          options={[
            { value: 'red', label: 'Red' },
            { value: 'green', label: 'Green' },
            { value: 'blue', label: 'Blue' },
            { value: 'yellow', label: 'Yellow' },
            { value: 'purple', label: 'Purple' },
          ]}
          multiple
          auto-set="label"
          onchange="handleComboChange('demoComboMultiple', 'demoComboMultipleValue', event)"
        />
        <div style="margin-top: 0.5rem;">
          <strong>Selected values:</strong>{' '}
          <span id="demoComboMultipleValue" style="color: #0054e9;">
            None
          </span>
        </div>
        <button
          style="margin-top: 0.5rem;"
          onclick="console.log('Selected values:', demoComboMultiple.value)"
        >
          Log Selected Values
        </button>
      </fieldset>
    </div>

    <h2>Search Behavior</h2>
    <p>
      By default, search matches against the <strong>label</strong>, not the{' '}
      {code('value')}. You can provide custom search text using the{' '}
      {code('search')} property to match alternative names or aliases.
    </p>
    <p>
      <strong>Example:</strong> With{' '}
      {code('search: "US USA United States America"')}, searching for any of
      these terms will match: "US", "USA", "United States", or "America".
    </p>
    <div class="code-demo">
      <fieldset>
        <legend>Example Source Code</legend>
        <code class="language-tsx" style="padding: 0.5rem">
          {
            /* html */ `
<ComboBox
  placeholder="Search countries..."
  options={[
    // search property allows matching multiple terms
    // Try searching: "US", "USA", "United States", or "America" - all will match
    { value: 'us', label: 'United States', search: 'US USA United States America' },
    { value: 'uk', label: 'United Kingdom', search: 'UK Britain United Kingdom' },
    { value: 'jp', label: 'Japan', search: 'JP Japan Nihon' },
  ]}
  auto-set="label"
  onchange="console.log('Selected:', event.detail.value)"
/>
`.trim()
          }
        </code>
      </fieldset>
      <fieldset>
        <legend>Preview</legend>
        <ComboBox
          id="demoComboSearch"
          placeholder="Search countries..."
          options={[
            {
              value: 'us',
              label: 'United States',
              search: 'US USA United States America',
            },
            {
              value: 'uk',
              label: 'United Kingdom',
              search: 'UK Britain United Kingdom',
            },
            { value: 'jp', label: 'Japan', search: 'JP Japan Nihon' },
            {
              value: 'kr',
              label: 'South Korea',
              search: 'Korea Republic South Korea',
            },
            { value: 'cn', label: 'China', search: 'China PRC Mainland' },
          ]}
          auto-set="label"
          onchange="handleComboChange('demoComboSearch', 'demoComboSearchValue', event)"
        />
        <div style="margin-top: 0.5rem;">
          <strong>Selected value:</strong>{' '}
          <span id="demoComboSearchValue" style="color: #0054e9;">
            None
          </span>
        </div>
      </fieldset>
    </div>

    <h2>Case Sensitive Search</h2>
    <p>
      By default, search is case-insensitive. Set {caseSensitive} attribute to
      enable case-sensitive matching.
    </p>
    <div class="code-demo">
      <fieldset>
        <legend>Example Source Code</legend>
        <code class="language-tsx" style="padding: 0.5rem">
          {
            /* html */ `
<ComboBox
  placeholder="Case sensitive search..."
  options={[
    { value: 'js', label: 'JavaScript' },
    { value: 'ts', label: 'TypeScript' },
    { value: 'py', label: 'Python' },
  ]}
  case-sensitive
  auto-set="label"
  onchange="console.log('Selected:', event.detail.value)"
/>
`.trim()
          }
        </code>
      </fieldset>
      <fieldset>
        <legend>Preview</legend>
        <ComboBox
          id="demoComboCase"
          placeholder="Case sensitive search..."
          options={[
            { value: 'js', label: 'JavaScript' },
            { value: 'ts', label: 'TypeScript' },
            { value: 'py', label: 'Python' },
            { value: 'go', label: 'Go' },
            { value: 'rs', label: 'Rust' },
          ]}
          case-sensitive
          auto-set="label"
          onchange="handleComboChange('demoComboCase', 'demoComboCaseValue', event)"
        />
        <div style="margin-top: 0.5rem;">
          <strong>Selected value:</strong>{' '}
          <span id="demoComboCaseValue" style="color: #0054e9;">
            None
          </span>
        </div>
      </fieldset>
    </div>

    <h2>Auto-Set Value</h2>
    <p>
      Use {autoSet} with {code('"value"')} to set the input's value to the
      option's value instead of the label. This is useful when you need to
      submit the form with the actual value.
    </p>
    <div class="code-demo">
      <fieldset>
        <legend>Example Source Code</legend>
        <code class="language-tsx" style="padding: 0.5rem">
          {
            /* html */ `
<form id="demoForm">
  <ComboBox
    name="language"
    placeholder="Select programming language..."
    options={[
      { value: 'javascript', label: 'JavaScript' },
      { value: 'typescript', label: 'TypeScript' },
      { value: 'python', label: 'Python' },
    ]}
    auto-set="value"
    onchange="console.log('Form value:', demoForm.language.value)"
  />
</form>
`.trim()
          }
        </code>
      </fieldset>
      <fieldset>
        <legend>Preview</legend>
        <form id="demoForm">
          <ComboBox
            name="language"
            placeholder="Select programming language..."
            options={[
              { value: 'javascript', label: 'JavaScript' },
              { value: 'typescript', label: 'TypeScript' },
              { value: 'python', label: 'Python' },
              { value: 'java', label: 'Java' },
              { value: 'rust', label: 'Rust' },
            ]}
            auto-set="value"
            onchange="handleComboFormChange('demoForm', 'demoFormValue', event)"
          />
        </form>
        <div style="margin-top: 0.5rem;">
          <strong>Form value:</strong>{' '}
          <span id="demoFormValue" style="color: #0054e9;">
            None
          </span>
        </div>
        {Script(/* javascript */ `
// Check the form value
demoForm.addEventListener('submit', event => {
  event.preventDefault()
  console.log('Form submitted with language:', demoForm.language.value)
})
`)}
      </fieldset>
    </div>

    {Script(/* javascript */ `
function updateComboValue(wrapperId, displayId, values) {
  let display = document.getElementById(displayId)
  if (!display) return
  if (!values || values.length === 0) {
    display.textContent = 'None'
    return
  }
  if (Array.isArray(values)) {
    display.textContent = JSON.stringify(values)
  } else {
    display.textContent = String(values)
  }
}

function updateComboFormValue(formId, displayId, event) {
  let form = document.getElementById(formId)
  let display = document.getElementById(displayId)
  if (form && form.language && display) {
    display.textContent = form.language.value || 'None'
  }
}

// Helper to handle combo box change events safely
function handleComboChange(comboBoxId, displayId, event) {
  if (event && event.detail) {
    updateComboValue(comboBoxId, displayId, event.detail.value)
    console.log('Selected:', event.detail.value)
  } else {
    // Fallback: get value directly from combo-box element
    let comboBox = document.getElementById(comboBoxId)
    if (comboBox) {
      updateComboValue(comboBoxId, displayId, comboBox.value)
      console.log('Selected:', comboBox.value)
    }
  }
}

function handleComboFormChange(formId, displayId, event) {
  let form = document.getElementById(formId)
  if (form && form.language) {
    updateComboFormValue(formId, displayId, event)
    console.log('Form value:', form.language.value)
  }
}
`)}
    <SourceCode page="demo-inputs/demo-combo-box.tsx" />
  </>
)

export default content