UNPKG

@kitn.ai/chat

Version:

Framework-agnostic, Shadow-DOM web components for building AI chat interfaces — works in React, Vue, Angular, Svelte, or plain HTML. Authored in SolidJS.

200 lines (192 loc) 8.27 kB
/* Element-bundle stylesheet. Compiled to compiled.css and injected into each custom element's shadow root. Mirrors .storybook/styles.css but scans src/. */ @import "tailwindcss"; @import "../../theme.css"; @plugin "@tailwindcss/typography"; @source "../"; @layer base { /* Base typography is token-driven so a theme can restyle it. --kc-font-base is the family for ALL text/UI (named "base", not "sans", so a serif body works); --kc-tracking is global letter-spacing. Both pierce the Shadow DOM via the var() fallback, exactly like the --kc-color-* tokens. */ :host { display: block; font-family: var(--kc-font-base, ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif); letter-spacing: var(--kc-tracking, normal); } /* Code/monospace surfaces use --kc-font-code (named for its ROLE — code — not "mono", so any family works). Covers inline code, <pre>, and code blocks. */ code, kbd, samp, pre { font-family: var(--kc-font-code, ui-monospace, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace); } * { border-color: var(--color-border); } /* Native controls (date/time pickers, <select>, number spinners, the checkbox/radio fallbacks, scrollbars) must follow the kit's light/dark mode — NOT the OS — so e.g. the native calendar icon never renders black on a dark surface. `color-scheme` drives that native rendering; we set it from the resolved mode (the `.dark` scope the element factory toggles). */ :host { color-scheme: light; } .dark { color-scheme: dark; } /* One token-driven focus ring for EVERY focusable element in the shadow root. Blue by default (via --color-ring), consistent in light + dark, and overridable through --kc-color-ring. Uses `outline` (not a box-shadow ring) so it never clips on overflow and follows the element's radius; `:focus-visible` so it only appears for keyboard navigation, never on a mouse click. Components that render their own ring set `focus-visible:outline-none` (a utility in a later cascade layer) and win over this — so there's no double ring, just a guaranteed blue fallback for the many controls that have no explicit focus style. */ :focus-visible { outline: 2px solid var(--color-ring); outline-offset: 2px; } /* Consistent cross-platform scrollbars for EVERY scroll region inside the shadow root (message list, conversation list, menus, hover cards, code blocks, the prompt textarea …). Thin + rounded + subtle, themed via tokens for light/dark, thumb stronger on hover. Track stays transparent and overflow is untouched, so no scrollbar is forced where none is needed. Scoped to the shadow root, so it never restyles the host page. */ * { scrollbar-width: thin; scrollbar-color: var(--color-scrollbar-thumb) transparent; } *::-webkit-scrollbar { width: 8px; height: 8px; } *::-webkit-scrollbar-track { background: transparent; } *::-webkit-scrollbar-thumb { background-color: var(--color-scrollbar-thumb); border-radius: 9999px; border: 2px solid transparent; background-clip: padding-box; } *::-webkit-scrollbar-thumb:hover { background-color: var(--color-scrollbar-thumb-hover); } *::-webkit-scrollbar-corner { background: transparent; } } @layer components { /* Designed radio + checkbox (appearance-none) so the form controls read as intentional, theme correctly in light/dark, and have comfortable targets — replacing the weak native defaults. Used via `.kc-radio` / `.kc-checkbox`. */ .kc-radio, .kc-checkbox { appearance: none; -webkit-appearance: none; inline-size: 1.125rem; block-size: 1.125rem; margin: 0; flex-shrink: 0; cursor: pointer; border: 1.5px solid var(--color-input); background-color: var(--color-background); display: inline-grid; place-content: center; box-shadow: 0 1px 2px rgb(0 0 0 / 0.04); transition: border-color 0.18s ease, background-color 0.18s ease, box-shadow 0.18s ease, transform 0.1s ease; } .kc-radio { border-radius: 9999px; } .kc-checkbox { border-radius: 0.3rem; } .kc-radio::after { content: ""; inline-size: 0.5rem; block-size: 0.5rem; border-radius: 9999px; background-color: var(--color-primary); transform: scale(0); transition: transform 0.2s cubic-bezier(0.34, 1.56, 0.64, 1); } .kc-radio:checked { border-color: var(--color-primary); } .kc-radio:checked::after { transform: scale(1); } .kc-checkbox:checked { background-color: var(--color-primary); border-color: var(--color-primary); } .kc-checkbox::after { content: ""; inline-size: 0.8rem; block-size: 0.8rem; background-color: var(--color-primary-foreground); transform: scale(0); transition: transform 0.2s cubic-bezier(0.34, 1.56, 0.64, 1); -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='3.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M20 6 9 17l-5-5'/%3E%3C/svg%3E") center / contain no-repeat; mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='3.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M20 6 9 17l-5-5'/%3E%3C/svg%3E") center / contain no-repeat; } .kc-checkbox:checked::after { transform: scale(1); } .kc-radio:hover:not(:disabled), .kc-checkbox:hover:not(:disabled) { border-color: var(--color-primary); box-shadow: 0 0 0 4px color-mix(in srgb, var(--color-primary) 9%, transparent); } .kc-radio:active:not(:disabled), .kc-checkbox:active:not(:disabled) { transform: scale(0.92); } .kc-checkbox:checked { box-shadow: 0 1px 4px color-mix(in srgb, var(--color-primary) 26%, transparent); } .kc-radio:disabled, .kc-checkbox:disabled { opacity: 0.5; cursor: not-allowed; } /* Custom range slider: a filled track (--kc-range-fill set by the widget) and a refined thumb that scales on hover/press with a focus halo — no native chrome. */ .kc-range { appearance: none; -webkit-appearance: none; inline-size: 100%; block-size: 1.25rem; background: transparent; cursor: pointer; margin: 0; } .kc-range::-webkit-slider-runnable-track { block-size: 0.375rem; border-radius: 9999px; border: none; box-shadow: none; background: linear-gradient( to right, var(--color-primary) var(--kc-range-fill, 0%), var(--color-muted) var(--kc-range-fill, 0%) ); } .kc-range::-moz-range-track { block-size: 0.375rem; border-radius: 9999px; border: none; box-shadow: none; background: var(--color-muted); } .kc-range::-moz-range-progress { block-size: 0.375rem; border-radius: 9999px; background: var(--color-primary); } .kc-range::-webkit-slider-thumb { appearance: none; -webkit-appearance: none; inline-size: 1.125rem; block-size: 1.125rem; border-radius: 9999px; background: var(--color-background); border: 2px solid var(--color-primary); box-shadow: 0 1px 3px rgb(0 0 0 / 0.18); margin-block-start: -0.375rem; transition: transform 0.12s ease, box-shadow 0.12s ease; } .kc-range::-moz-range-thumb { inline-size: 1.125rem; block-size: 1.125rem; border-radius: 9999px; background: var(--color-background); border: 2px solid var(--color-primary); box-shadow: 0 1px 3px rgb(0 0 0 / 0.18); transition: transform 0.12s ease, box-shadow 0.12s ease; } .kc-range:hover::-webkit-slider-thumb { transform: scale(1.12); } .kc-range:hover::-moz-range-thumb { transform: scale(1.12); } .kc-range:focus-visible { outline: none; } .kc-range:focus-visible::-webkit-slider-thumb, .kc-range:active::-webkit-slider-thumb { box-shadow: 0 0 0 6px color-mix(in srgb, var(--color-primary) 14%, transparent); } .kc-range:focus-visible::-moz-range-thumb, .kc-range:active::-moz-range-thumb { box-shadow: 0 0 0 6px color-mix(in srgb, var(--color-primary) 14%, transparent); } .kc-range:disabled { opacity: 0.5; cursor: not-allowed; } }