Kosmesis
Components

Input Group

Pairs an Input with leading/trailing addons. Purely presentational.

Installation

npx kosmesis add input-group
pnpm dlx kosmesis add input-group
yarn dlx kosmesis add input-group
bunx kosmesis add input-group

Copy and paste the following code into your project.

input-group.tsx
import { StatelessComponent } from "@praxisjs/core";import { Component } from "@praxisjs/decorators";import type { Children } from "@praxisjs/shared";import { cn } from "@/lib/utils";export interface InputGroupProps {  class?: string;  id?: string;  children?: Children;}/** Purely presentational — no Morphos equivalent, same as upstream shadcn/ui. Pair with `Input`. */@Component()export class InputGroup extends StatelessComponent<InputGroupProps> {  render() {    const { class: cls, id, children } = this.props;    return (      <div        id={id}        class={cn(          "group/input-group flex h-9 w-full items-center rounded-md border border-input bg-transparent shadow-xs transition-[color,box-shadow]",          "has-[[data-focused]]:border-ring has-[[data-focused]]:ring-[3px] has-[[data-focused]]:ring-ring/50",          "has-[[data-invalid]]:border-destructive has-[[data-invalid]]:ring-destructive/20",          "[&>input]:h-full [&>input]:flex-1 [&>input]:border-0 [&>input]:bg-transparent [&>input]:px-3 [&>input]:shadow-none [&>input]:outline-none [&>input]:focus-visible:ring-0",          cls,        )}      >        {children}      </div>    );  }}export interface InputGroupAddonProps {  align?: "start" | "end";  class?: string;  children?: Children;}@Component()export class InputGroupAddon extends StatelessComponent<InputGroupAddonProps> {  render() {    const { align = "start", class: cls, children } = this.props;    return (      <div        data-align={align}        class={cn(          "flex items-center gap-2 px-3 text-muted-foreground [&_svg]:size-4",          align === "start" && "order-first",          align === "end" && "order-last",          cls,        )}      >        {children}      </div>    );  }}export interface InputGroupTextProps {  class?: string;  children?: Children;}@Component()export class InputGroupText extends StatelessComponent<InputGroupTextProps> {  render() {    const { class: cls, children } = this.props;    return <span class={cn("text-sm text-muted-foreground", cls)}>{children}</span>;  }}

Install the following dependencies:

npm install @praxisjs/css
pnpm add @praxisjs/css
yarn add @praxisjs/css
bun add @praxisjs/css

Copy and paste the following code into your project.

input-group.tsx
import { StatelessComponent } from "@praxisjs/core";import { cx, Stylesheet, Styled, tokenVars } from "@praxisjs/css";import { Component } from "@praxisjs/decorators";import type { Children } from "@praxisjs/shared";import { KosmesisTokens } from "@/lib/kosmesis-theme";const t = tokenVars(KosmesisTokens);class InputGroupStyles extends Stylesheet {  $root = this.css({    display: "flex",    height: "2.25rem",    width: "100%",    alignItems: "center",    borderRadius: `calc(${t.radius} - 2px)`,    border: `1px solid ${t.input}`,    backgroundColor: "transparent",    boxShadow: "0 1px 2px 0 rgb(0 0 0 / 0.05)",    transition: "color 120ms ease, box-shadow 120ms ease",  })    .has("[data-focused]", { borderColor: t.ring, boxShadow: `0 0 0 3px color-mix(in oklab, ${t.ring} 50%, transparent)` })    .has("[data-invalid]", { borderColor: t.destructive, boxShadow: `0 0 0 3px color-mix(in oklab, ${t.destructive} 20%, transparent)` })    .on("& > input", {      height: "100%",      flex: "1 1 0%",      border: "0",      backgroundColor: "transparent",      padding: "0 0.75rem",      boxShadow: "none",      outline: "none",    });  $addon = this.css({ display: "flex", alignItems: "center", gap: "0.5rem", padding: "0 0.75rem", color: t.mutedForeground })    .on("& svg", { width: "1rem", height: "1rem" })    .on('&[data-align="start"]', { order: "-1" })    .on('&[data-align="end"]', { order: "1" });  $text = this.css({ fontSize: "0.875rem", color: t.mutedForeground });}export interface InputGroupProps {  class?: string;  id?: string;  children?: Children;}/** Purely presentational — no Morphos equivalent, same as upstream shadcn/ui. Pair with `Input`. */@Component()export class InputGroup extends StatelessComponent<InputGroupProps> {  @Styled(InputGroupStyles) $s!: InputGroupStyles;  render() {    const { class: cls, id, children } = this.props;    return (      <div id={id} class={cx(this.$s.$root, cls)}>        {children}      </div>    );  }}export interface InputGroupAddonProps {  align?: "start" | "end";  class?: string;  children?: Children;}@Component()export class InputGroupAddon extends StatelessComponent<InputGroupAddonProps> {  @Styled(InputGroupStyles) $s!: InputGroupStyles;  render() {    const { align = "start", class: cls, children } = this.props;    return (      <div data-align={align} class={cx(this.$s.$addon, cls)}>        {children}      </div>    );  }}export interface InputGroupTextProps {  class?: string;  children?: Children;}@Component()export class InputGroupText extends StatelessComponent<InputGroupTextProps> {  @Styled(InputGroupStyles) $s!: InputGroupStyles;  render() {    const { class: cls, children } = this.props;    return <span class={cx(this.$s.$text, cls)}>{children}</span>;  }}

Examples

Usage

import { Input } from "@/components/ui/input";
import { InputGroup, InputGroupAddon, InputGroupText } from "@/components/ui/input-group";

<InputGroup>
  <InputGroupAddon>
    <InputGroupText>$</InputGroupText>
  </InputGroupAddon>
  <Input type="number" placeholder="0.00" />
  <InputGroupAddon align="end">
    <InputGroupText>USD</InputGroupText>
  </InputGroupAddon>
</InputGroup>

On this page