Replies: 24 comments 26 replies
-
This looks good except it doesn't place the magnifying glass inside the input. This is because the classes for styling the input are directly on the input and not on the div surrounding both elements. Here's a screenshot. Considering this, I moved styles to an outer div and allowed the same kind of customization as the input. Some styles need to be on the root div like focus-within (within because we're focusing the input not the div) and alignment then some styles need to be on the input itself like padding to get the click target correct.
This snippet appears as such with icon inside the input. Note though it's not inside the input, it's inside the div which is styled to look like an input. |
Beta Was this translation helpful? Give feedback.
-
This is how i was able to get the icon in the inputarea
|
Beta Was this translation helpful? Give feedback.
-
I did this way,
and then can use like this:
|
Beta Was this translation helpful? Give feedback.
-
My solution: import React from 'react'
import { SearchIcon } from 'lucide-react'
import { cn } from '@/lib/utils'
export type InputProps = React.InputHTMLAttributes<HTMLInputElement>
const separatePaddingClasses = (className: string | undefined) => {
const inputClassesPrefixes = [
'p-',
'px-',
'py-',
'pl-',
'pr-',
'pt-',
'pb-',
'placeholder:',
]
const bothElementsClassesPrefixes = ['bg-']
const allClasses = className ? className.split(' ') : []
const bothElementsClasses = allClasses.filter((currentClass) =>
bothElementsClassesPrefixes.some((prefix) =>
currentClass.startsWith(prefix),
),
)
const inputClasses = allClasses.filter((currentClass) =>
inputClassesPrefixes.some((prefix) => currentClass.startsWith(prefix)),
)
const otherClasses = allClasses.filter(
(currentClass) =>
!inputClassesPrefixes.some((prefix) => currentClass.startsWith(prefix)),
)
return {
inputClasses: [...inputClasses, ...bothElementsClasses].join(' '),
otherClasses: [...otherClasses, ...bothElementsClasses].join(' '),
}
}
const SearchInput = React.forwardRef<HTMLInputElement, InputProps>(
({ className, placeholder, ...props }, ref) => {
const { inputClasses, otherClasses } = separatePaddingClasses(className)
return (
<div
className={cn(
'relative flex rounded-lg border border-input bg-transparent pr-3 text-sm text-input shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-input-placeholder focus-within:border-primary focus:outline-none disabled:cursor-not-allowed disabled:opacity-50',
otherClasses,
)}
>
<SearchIcon className="absolute right-4 top-1/2 z-10 -translate-y-1/2 transform text-primary" />
<input
type="text"
placeholder={placeholder}
className={cn(
'w-full rounded-bl-lg rounded-tl-lg border-0 py-2.5 pl-3 outline-none',
inputClasses,
)}
ref={ref}
{...props}
/>
</div>
)
},
)
SearchInput.displayName = 'Search'
export { SearchInput } The |
Beta Was this translation helpful? Give feedback.
-
You should be able to just compose it as such: import { inputVariants } from "@/components";
import React from "react";
import { SearchIcon } from "lucide-react";
export const Search: React.FC = () => {
return (
<label
className={inputVariants({
className: "[&:has(:focus-visible)]:ring-ring flex items-center p-0 [&:has(:focus-visible)]:ring-2",
})}
>
<span className="sr-only">Search</span>
<SearchIcon className="size-4" />
<input
type="search"
placeholder="Search"
className="size-full ml-2 border-none bg-transparent focus:outline-none"
/>
</label>
);
}; |
Beta Was this translation helpful? Give feedback.
-
I've just developed a solution that enhances input components. This solution allows for the seamless integration of icons with inputs, particularly adding support for icons in password inputs, complete with Caps Lock indication and eye icon toggling. "use client";
import * as React from "react";
import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from "@/components/ui/tooltip";
import { cn } from "@/lib/utils";
import { ArrowBigUpDash, EyeIcon, EyeOffIcon } from "lucide-react";
export interface InputProps
extends React.InputHTMLAttributes<HTMLInputElement> {
Icon?: React.ComponentType<React.SVGProps<SVGSVGElement>>;
}
const Input = React.forwardRef<HTMLInputElement, InputProps>(
({ Icon, className, type, ...props }, ref) => {
const [showPassword, setShowPassword] = React.useState(false);
const [capsLockActive, setCapsLockActive] = React.useState(false);
const handleKeyPress: React.KeyboardEventHandler<HTMLInputElement> = (
event
) => {
const capsLockOn = event.getModifierState("CapsLock");
setCapsLockActive(capsLockOn);
};
const togglePasswordVisibility = () => setShowPassword(!showPassword);
const inputClasses = cn(
"flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-sm shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50",
Icon && "pl-10",
type === "password" && (!capsLockActive ? "pr-8" : "pr-16"),
className
);
return (
<div className={cn("relative", className)}>
{Icon && (
<div className="absolute inset-y-0 left-0 flex items-center pl-3 pointer-events-none">
<Icon />
</div>
)}
<input
type={type === "password" && showPassword ? "text" : type}
className={inputClasses}
onKeyDown={handleKeyPress}
ref={ref}
{...props}
/>
{type === "password" && (
<div className="absolute right-0 flex items-center pr-3 -translate-y-1/2 top-1/2 gap-x-1">
{showPassword ? (
<EyeOffIcon
className="cursor-pointer"
onClick={togglePasswordVisibility}
size={20}
/>
) : (
<EyeIcon
className="cursor-pointer"
onClick={togglePasswordVisibility}
size={20}
/>
)}
{capsLockActive && type === "password" && (
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<ArrowBigUpDash size={20} />
</TooltipTrigger>
<TooltipContent>
<p>Caps Lock is on!</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
)}
</div>
)}
</div>
);
}
);
Input.displayName = "Input";
export { Input }; |
Beta Was this translation helpful? Give feedback.
-
@F-47 Thanks for the implementation, it's great! I changed the Icon prop to be Also, in the outermost div, I added |
Beta Was this translation helpful? Give feedback.
-
This is what I did with my implimentation, this way you can customize the icon however you like. For example I can add a
|
Beta Was this translation helpful? Give feedback.
-
This is an input field for entering a password. The icon next to it toggles the visibility of the password. import * as React from "react";
import { cn } from "@/lib/utils";
export interface InputProps
extends React.InputHTMLAttributes<HTMLInputElement> {
icon?: React.ReactNode;
onShow?: () => void;
}
const Input = React.forwardRef<HTMLInputElement, InputProps>(
({ className, type, icon, onShow, ...props }, ref) => {
return (
<div
className={cn(
"flex h-10 items-center rounded-md border border-input bg-white pl-3 text-sm ring-offset-background focus-within:ring-1 focus-within:ring-ring focus-within:ring-offset-2",
className
)}>
<input
type={type}
className="w-full p-2 placeholder:text-muted-foreground focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50"
ref={ref}
{...props}
/>
<span onClick={onShow} className="pr-4 cursor-pointer">
{icon}
</span>
</div>
);
}
);
Input.displayName = "Input";
export { Input }; |
Beta Was this translation helpful? Give feedback.
-
import * as React from "react"; export interface InputProps const Input = React.forwardRef<HTMLInputElement, InputProps>( {icon && iconPosition === "left" && ( {icon} )} <input type={type} className={cn( "flex h-10 w-full rounded-md border border-input bg-white py-2 pl-14 pr-12 text-sm ring-offset-primary file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-0 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50", className, )} ref={ref} {...props} /> {icon && iconPosition === "right" && ( {icon} )} ); }, ); Input.displayName = "Input"; export { Input }; |
Beta Was this translation helpful? Give feedback.
-
import * as React from "react" import { cn } from "@/lib/utils" export interface InputProps const Input = React.forwardRef<HTMLInputElement, InputProps>( {props.icon && ( <props.icon size={20} className="absolute opacity-65 left-3 top-1/2 transform -translate-y-1/2 text-muted-foreground" /> )} <input type={type !== "abn" ? type : "number"} onChange={props.onChange} className={cn( "flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-primary disabled:cursor-not-allowed disabled:opacity-50", props.icon && "pl-10", className )} ref={ref} min={0} {...props} /> ) } ) Input.displayName = "Input" export { Input } |
Beta Was this translation helpful? Give feedback.
-
Tried to make a solution using composition pattern, but couldn't get the focus state for the father div right (using Expo/Reusables). import { cva, type VariantProps } from 'class-variance-authority'
import type { LucideProps } from 'lucide-react-native'
import * as React from 'react'
import { TextInput, View } from 'react-native'
import { cn } from '~/lib/utils'
const inputContainerVariants = cva(
'flex flex-row items-center group web:group-focus-visible:border-4 ring-2 ring-input bg-background px-[10px] py-[7px] group web:focus-within:ring-2 web:group-focus-visible:ring-2 web:ring-offset-background',
{
variants: {
radius: {
default: 'rounded-[100px]',
sm: 'rounded-[10px]',
},
},
defaultVariants: {
radius: 'default',
},
}
)
type InputContainerProps = React.ComponentPropsWithoutRef<typeof View> &
VariantProps<typeof inputContainerVariants> & {
children: React.ReactNode
}
const InputContainer = React.forwardRef<React.ElementRef<typeof View>, InputContainerProps>(
({ children, className, radius, ...props }, ref) => {
return (
<View
ref={ref}
className={cn(inputContainerVariants({ className, radius }))}
{...props}
>
{children}
</View>
)
}
)
InputContainer.displayName = 'InputContainer'
type InputIconProps = { icon: React.ComponentType<LucideProps> } & LucideProps
const InputIcon = ({ icon: Icon, size = 20, ...props }: InputIconProps) => {
return (
<View className='p-2.5 -mb-1'>
<Icon
className='text-primary'
size={size}
{...props}
/>
</View>
)
}
InputIcon.displayName = 'InputIcon'
const Input = React.forwardRef<
React.ElementRef<typeof TextInput>,
React.ComponentPropsWithoutRef<typeof TextInput>
>(({ className, placeholderClassName, ...props }, ref) => {
return (
<TextInput
ref={ref}
className={cn(
'web:flex h-10 native:h-12 web:w-full rounded-md px-2.5 mr-2.5 group web:py-2.5 text-base lg:text-sm group native:text-lg native:leading-[1.25] text-foreground placeholder:text-muted-foreground web:ring-offset-background file:border-0 file:bg-transparent file:font-medium web:focus-visible:outline-none web:focus-visible:ring-0',
props.editable === false && 'opacity-50 web:cursor-not-allowed',
className
)}
placeholderClassName={cn('text-muted-foreground', placeholderClassName)}
{...props}
/>
)
})
Input.displayName = 'Input'
export { Input, InputContainer, InputIcon } |
Beta Was this translation helpful? Give feedback.
-
I have a simple extension of the base icon component, with support for Start icons, End icons, Password hide/reveal functionality and CapsLock active indication. ` export interface InputProps const Input = React.forwardRef<HTMLInputElement, InputProps>(
} export { Input }; |
Beta Was this translation helpful? Give feedback.
-
🙌 My Solution for Input password with toggle Eye Icon using shadcn/ui and radix-ui/react-icons 👁.import * as React from "react" import { cn } from "@/lib/utils"; export interface InputProps {/* NORMAL INPUT */}
} {/* PASSWORD INPUT WITH TOGGLE EYE ICON*/} ({ className, type, ...props }, ref) => {
} Input.displayName = "Input"; export { Input, InputPassword } |
Beta Was this translation helpful? Give feedback.
-
My solution with both startIcon and endIcon const Input = React.forwardRef(({ className, type, startIcon, endIcon, onStartIconClick, onEndIconClick, ...props }, ref) => {
const StartIcon = startIcon;
const EndIcon = endIcon;
return (
<div className="w-full relative flex items-center">
{startIcon && <button
className="absolute left-3 text-center transition-all disabled:pointer-events-none disabled:opacity-50"
type="button"
onClick={onStartIconClick}
>
<StartIcon className={"w-4 h-4 text-foreground-faded"} />
</button>
}
<input
type={type}
className={cn("w-full py-2 rounded-sm bg-background placeholder:text-foreground-faded border border-border-faded transition duration-300 ease focus:outline-none focus:border-border-primary",
startIcon && 'pl-9',
endIcon && 'pr-9',
className
)}
ref={ref}
{...props }
/>
{endIcon && <button
className="absolute right-3 text-center transition-all disabled:pointer-events-none disabled:opacity-50"
type="button"
onClick={onEndIconClick}
>
<EndIcon className="w-4 h-4 text-foreground" />
</button>
}
</div>
);
})
Input.displayName = "Input"
export { Input } |
Beta Was this translation helpful? Give feedback.
-
Hi all, I've created a PR to make it easier to use icons |
Beta Was this translation helpful? Give feedback.
-
A bit late but heres how i did it import { cn } from "@/lib/utils"; export interface InputProps {icon && ( {icon} )}{" "} {/* Render icon if provided */} <input type={type} className={cn( flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 ${icon && "pl-8"} ,className, )} ref={ref} {...props} /> ); }, ); Input.displayName = "Input"; export { Input }; |
Beta Was this translation helpful? Give feedback.
-
The most flexible solution I've found: import * as React from 'react'
import { cn } from '~/lib/utils'
type IconProps = React.SVGProps<SVGSVGElement> & { children?: never }
type IconPropsWithBehavior<T extends IconProps> = T & { behavior: 'append' | 'prepend' }
type IconComponent<T extends IconProps = IconProps> = React.ComponentType<T>
export type InputProps<T extends IconComponent = IconComponent> = React.InputHTMLAttributes<HTMLInputElement> & {
icon?: T
iconProps: T extends IconComponent<infer P> ? IconPropsWithBehavior<P> : never
}
const Input = React.forwardRef<HTMLInputElement, InputProps>(
({ className, type, icon, iconProps: { behavior: iconBehavior, className: iconClassName, ...iconProps }, ...props }, ref) => {
const Icon = icon
return (
<div
className={cn(
'flex items-center justify-center m-0 p-0 rounded-md border border-input bg-transparent px-3 py-0 text-sm shadow-sm transition-colors focus-within:outline-none focus-within:ring-1 focus-within:ring-ring disabled:cursor-not-allowed disabled:opacity-50',
className
)}
>
{Icon && type !== 'file' && iconBehavior === 'prepend' && (
<Icon className={cn('w-4 h-4 mr-3 text-muted-foreground', iconClassName)} {...iconProps} />
)}
<input
type={type}
className={cn(
'flex items-center justify-center h-9 w-full bg-transparent placeholder:text-muted-foreground file:border-0 file:bg-transparent file:text-sm file:font-medium focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50',
type !== 'file' ? 'py-1' : 'py-1.5',
className
)}
ref={ref}
{...props}
/>
{Icon && type !== 'file' && iconBehavior === 'append' && (
<Icon className={cn('w-4 h-4 ml-3 text-muted-foreground', iconClassName)} {...iconProps} />
)}
</div>
)
}
)
Input.displayName = 'Input'
export { Input } Usage: import { Input } from '~/components/ui/input'
import { MagnifyingGlassIcon as SearchIcon } from '@radix-ui/react-icons'
<Input type='search' icon={SearchIcon} iconProps={{ behavior: 'prepend' }} placeholder='Search...' /> Preview: |
Beta Was this translation helpful? Give feedback.
-
Below is my proposal. import * as React from "react"
import { cn } from "@/utils/utils"
import { ArrowBigUpDash, Eye, EyeClosed, Search } from "lucide-react";
import { useKeyPress } from "ahooks";
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "./tooltip";
type InputProps = React.PropsWithChildren<React.ComponentProps<"input">> & {
icon?: React.ReactNode
}
const Input = React.forwardRef < HTMLInputElement, InputProps> (
({ children, icon, className, type, ...props }, ref) => {
const classes = cn(
'flex h-10 w-full gap-x-2 rounded-md border border-input bg-background px-3 py-2 text-base has-[:focus-visible]:outline-none has-[:focus-visible]:ring-2 has-[:focus-visible]:ring-ring has-[:focus-visible]:ring-offset-2 has-[:disabled]:cursor-not-allowed has-[:disabled]:opacity-50 md:text-sm',
className
);
return (
<div className={classes}>
{icon}
<input
type={type}
className="flex-1 h-full bg-background outline-none file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground disabled:cursor-not-allowed"
ref={ref}
{...props}
/>
{children}
</div>
)
}
)
Input.displayName = "Input"
const InputPassword = React.forwardRef < HTMLInputElement, Omit<React.ComponentProps<'input' >, 'type' >> (
(props, ref) => {
const [showPassword, setShowPassword] = React.useState(false);
const togglePasswordVisibility = () => setShowPassword(!showPassword);
const [capsLockActive, setCapsLockActive] = React.useState(false);
useKeyPress(
() => true,
e => {
if (e.getModifierState) {
setCapsLockActive(e.getModifierState('CapsLock'))
}
}
);
return (
<Input ref={ref} {...props} type={showPassword ? "text" : 'password'}>
<div className="flex items-center gap-x-1 self-center">
{showPassword ? (
<EyeClosed
className="cursor-pointer"
onClick={togglePasswordVisibility}
size={20}
/>
) : (
<Eye
className="cursor-pointer"
onClick={togglePasswordVisibility}
size={20}
/>
)}
{capsLockActive && (
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<ArrowBigUpDash size={20} />
</TooltipTrigger>
<TooltipContent>
<p>Caps Lock is on!</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
)}
</div>
</Input>
);
}
);
InputPassword.displayName = 'InputPassword';
const InputSearch = React.forwardRef < HTMLInputElement, React.ComponentProps< 'input' >> (
(props, ref) => {
return <Input ref={ref} {...props} icon={<Search size={20} className="self-center" />} />;
}
);
export { Input, InputPassword, InputSearch } |
Beta Was this translation helpful? Give feedback.
-
My implementation: import { forwardRef, ComponentProps, ReactNode, cloneElement, ReactElement } from 'react';
import { cn } from '@/lib/utils';
// Types
type IconProps = {
icon: ReactNode;
position: 'left' | 'right';
};
/**
* Input Icon
*/
const InputIcon = (props: IconProps) => {
const { icon, position } = props;
const positionClasses = {
left: 'absolute left-1.5 top-1/2 -translate-y-1/2 transform',
right: 'absolute right-3 top-1/2 -translate-y-1/2 transform',
};
if (!icon) return null;
return (
<div className={positionClasses[position]}>
{cloneElement(icon as ReactElement, {
size: 18,
className: 'text-[--muted-foreground]',
})}
</div>
);
};
type InputProps = ComponentProps<'input'> & {
startIcon?: ReactNode;
endIcon?: ReactNode;
};
/**
* Icon
*/
export const Input = forwardRef<HTMLInputElement, InputProps>((props, ref) => {
const { className, type, startIcon, endIcon, ...rest } = props;
return (
<div className="relative w-full">
<InputIcon icon={startIcon} position="left" />
<input
type={type}
className={cn(
'border-input ring-offset-background focus-visible:ring-ring flex h-10 w-full rounded-md border bg-[--background] px-4 py-2 text-sm file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-[--muted-foreground] focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-offset-0 disabled:cursor-not-allowed disabled:opacity-50',
{ 'pl-8': startIcon, 'pr-8': endIcon },
className,
)}
ref={ref}
{...rest}
/>
<InputIcon icon={endIcon} position="right" />
</div>
);
});
Input.displayName = 'Input'; |
Beta Was this translation helpful? Give feedback.
-
My implementation: import { cn } from '@/lib/utils';
import * as React from 'react';
export interface InputProps extends React.ComponentProps<'input'> {
left?: React.ReactNode;
right?: React.ReactNode;
}
const Input = React.forwardRef<HTMLInputElement, InputProps>(
({ className, type, left, right, ...props }, ref) => {
return (
<div
className={cn(
'flex-row gap-3 flex h-9 w-full items-center px-3 rounded-md border border-input bg-transparent shadow-sm transition-colors focus-within:ring-1 focus-within:ring-ring disabled:opacity-50',
className,
)}
>
{left && (
<div className="flex h-full items-center text-muted-foreground [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0">
{left}
</div>
)}
<input
ref={ref}
className={cn(
'h-full flex-1 bg-transparent text-sm placeholder:text-muted-foreground focus:outline-none disabled:cursor-not-allowed',
)}
type={type}
{...props}
/>
{right && (
<div className="h-full flex items-center text-muted-foreground [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0">
{right}
</div>
)}
</div>
);
},
);
Input.displayName = 'Input';
export { Input }; Example: <Input
name="input"
left={<UserRound />}
right={
<Button
className="p-0"
variant="link"
size="sm"
onClick={() => {
console.log('Clicked');
}}
>
Generate
</Button>
}
/> |
Beta Was this translation helpful? Give feedback.
-
My approach is slightly different. This is using React 19, Tailwind v4, etc. Feel free to criticize or suggest improvements. import * as React from "react";
import { cva, VariantProps } from "class-variance-authority";
import { cn } from "~/lib/utils";
const inputVariants = cva(
cn(
"border-input flex flex-row items-center gap-2 text-base",
"transition-[color,box-shadow,border]",
"[&>input]:placeholder:text-muted-foreground [&>input]:focus:outline-0",
"[&>input]:file:text-foreground/50 [&>input]:file:mr-2 [&>input]:file:inline-flex [&>input]:file:text-sm [&>input]:file:font-medium",
"[&>svg]:text-muted-foreground",
"selection:bg-primary selection:text-primary-foreground",
),
{
variants: {
variant: {
outline: cn(
"rounded-md",
"has-focus:border-ring has-focus-visible:border-ring has-focus-visible:ring-ring/50 has-focus-visible:ring-[3px]",
"has-aria-invalid:ring-destructive/20 has-aria-invalid:border-destructive dark:has-aria-invalid:ring-destructive/40 has-aria-invalid:border-1",
),
},
size: {
default: "px-2 py-1",
sm: "px-2 text-sm py-1",
lg: "px-3 text-lg py-2",
xl: "px-3 text-xl py-2",
},
},
defaultVariants: {
variant: "outline",
size: "default",
},
},
);
function Input({
className,
variant,
size,
children,
...props
}: React.ComponentProps<"div"> & VariantProps<typeof inputVariants>) {
return (
<div className={cn(inputVariants({ variant, size }), className)} {...props}>
{children}
</div>
);
}
export { Input }; Usage: (
<label>
<Input>
<Search size={16} />
<input type="search" data-slot="search" placeholder="Search" />
</Input>
</label>
) |
Beta Was this translation helpful? Give feedback.
-
This is what I landed on, it felt the most similar to how other components are setup and keeps the icon specific code outside of the base icon component. I like positioning the icon over the input so that the focus outline remains around the border of the input and not somewhere inside. import * as React from "react";
import { cn } from "@/lib/utils";
import { Slot } from "@radix-ui/react-slot";
import { cva, VariantProps } from "class-variance-authority";
export interface InputRootProps extends React.ComponentProps<"div"> {}
function InputRoot({ children, className, ...props }: InputRootProps) {
return (
<div className={cn("relative", className)} {...props}>
{children}
</div>
);
}
function InputIcon({
children,
className,
}: {
children: React.ReactNode;
className?: string;
}) {
return (
<Slot
role="presentation"
className={cn(
"absolute left-3 top-2 bottom-2 pointer-events-none size-5 [&~input]:pl-11",
className
)}
>
{children}
</Slot>
);
}
const inputVariants = cva(
"flex h-9 w-full rounded-md bg-transparent border px-3 py-1 text-base transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:outline-hidden focus-visible:ring-2 disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
{
variants: {
variant: {
default: "border-input shadow-xs focus-visible:ring-ring",
destructive:
"border-destructive shadow-xs focus-visible:ring-destructive",
ghost: "border-transparent -mx-3 -my-1 focus-visible:ring-ring",
},
},
defaultVariants: {
variant: "default",
},
}
);
export interface InputProps
extends React.ComponentProps<"input">,
VariantProps<typeof inputVariants> {
ref?: React.ForwardedRef<HTMLInputElement>;
}
function Input({ className, type, ref, variant, ...props }: InputProps) {
return (
<input
type={type}
className={cn(inputVariants({ variant }), className)}
ref={ref}
{...props}
/>
);
}
export { Input, InputIcon, InputRoot, inputVariants }; This doesn't alter the original input styles or component so it's easy to implement. Usage is like: import { Search } from "lucide-react";
import { Input, InputIcon, InputRoot } from "./ui/input";
export default async function AppHeader() {
return (
<div className="flex items-center justify-between h-14 border-b sticky top-0 bg-white z-10">
<InputRoot>
<InputIcon>
<Search />
</InputIcon>
<Input />
</InputRoot>
</div>
);
} |
Beta Was this translation helpful? Give feedback.
-
Simple solution import { Search } from "lucide-react";
import { Input } from "@/components/ui/input";
export function InputWithIcon() {
return (
<Fragment>
<Input
type="text"
placeholder="Search"
className="peer block w-full rounded-md border py-[9px] pl-10 text-sm"
/>
<Search className="pointer-events-none absolute left-3 top-1/2 h-[18px] w-[18px] -translate-y-1/2" />
</Fragment>
)
} |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
-
Do there have official way to do the input with icon?

I got
my implementation but I would like to know is there have the proper way to do it?
Beta Was this translation helpful? Give feedback.
All reactions