shadimage
odysseyui
uicomponents-ai-prompt-inputsource: ok · hit

Prompt Input

Simple Prompt Interface input design.

npx shadcn add "https://www.odysseyui.com/r/components-ai-prompt-input.json"
free

No selection tags yet — empty means no evidence, not lack of support.

Dependencies

Bundle weight · light
@hugeicons/core-free-icons@hugeicons/react

Files1 · source cached 10/9/2026, 1:20:50 AM

registry/components/ai/prompt-input/index.tsx
'use client';

import { useState, useRef, useLayoutEffect } from 'react';
import { HugeiconsIcon } from '@hugeicons/react';
import {
  ArrowUp02Icon,
  Attachment,
  Earth,
  Mic,
} from '@hugeicons/core-free-icons';
import { Button } from '../../buttons/button';

export default function PromptInput() {
  const [value, setValue] = useState('');
  const [searchEnabled, setSearchEnabled] = useState(false);
  const fileInputRef = useRef<HTMLInputElement>(null);
  const textareaRef = useRef<HTMLTextAreaElement>(null);

  // Auto-resize textarea
  useLayoutEffect(() => {
    const el = textareaRef.current;
    if (!el) return;
    el.style.height = 'auto';
    el.style.height = `${Math.min(el.scrollHeight, 240)}px`;
  }, [value]);

  const handleKeyDown = (e: React.KeyboardEvent<HTMLTextAreaElement>) => {
    if (e.key === 'Enter' && !e.shiftKey) {
      e.preventDefault();
      // handle submit
    }
  };

  return (
    <div className="absolute inset-x-0 bottom-0 mx-auto max-w-3xl px-3 pb-4 md:px-5 md:pb-0">
      {/* Outer wrapper */}
      <div className="border-border/50 bg-input/50 border border-b-0 p-1.5 pb-0 rounded-4xl rounded-b-none backdrop-blur-sm">
        {/* Inner card */}
        <div className="border-input bg-popover/50 relative z-10 w-full rounded-3xl rounded-b-none border border-b-0 p-0 pt-1 shadow-xs">
          <div className="flex flex-col">
            {/* Textarea */}
            <textarea
              ref={textareaRef}
              value={value}
              onChange={(e) => setValue(e.target.value)}
              onKeyDown={handleKeyDown}
              placeholder="Ask anything..."
              rows={1}
              className="text-[#096cdc] min-h-[44px] w-full resize-none border-none bg-transparent shadow-none outline-none focus-visible:ring-0 focus-visible:ring-offset-0 min-h-16 pt-3 pl-4 pr-4 text-base leading-[1.3] sm:text-base md:text-base"
              style={{ maxHeight: 240 }}
            />

            {/* Action bar */}
            <div className="mt-4 flex w-full items-center justify-between gap-2 px-3 pb-5">
              {/* Left actions */}
              <div className="flex items-center gap-2">
                {/* Attachment */}
                <Button
                  variant="outline"
                  size="icon"
                  className="size-9 rounded-full"
                  onClick={() => fileInputRef.current?.click()}
                  type="button"
                >
                  <HugeiconsIcon
                    icon={Attachment}
                    strokeWidth={2}
                    className="rotate-45"
                  />
                </Button>

                {/* Search toggle */}

                <Button
                  className={`rounded-full px-3 transition-colors bg-background text-black dark:text-white hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50 ${
                    searchEnabled
                      ? 'bg-blue-glossy text-white hover:text-white'
                      : ''
                  }`}
                  onClick={() => setSearchEnabled((v) => !v)}
                  type="button"
                >
                  <HugeiconsIcon icon={Earth} strokeWidth={2} />
                  Search
                </Button>
              </div>

              <div className="flex items-center gap-2">
                <Button
                  variant="outline"
                  size="icon"
                  className="size-9 rounded-full"
                  type="button"
                >
                  <HugeiconsIcon icon={Mic} strokeWidth={2} />
                </Button>

                <Button
                  size="icon"
                  className="size-9  bg-[#096cdc] hover:bg-blue-glossy"
                >
                  <HugeiconsIcon icon={ArrowUp02Icon} strokeWidth={2} />
                </Button>
              </div>
            </div>
          </div>
        </div>
      </div>

      <input
        ref={fileInputRef}
        type="file"
        className="hidden"
        multiple
        onChange={(e) => {
          if (!e.target.files) return;
          // handle files: Array.from(e.target.files)
          e.target.value = '';
        }}
      />
    </div>
  );
}

More from odysseyui

Browse all components in this library →