Snapshot before v2 rewrite

This commit is contained in:
Mats Bosson 2026-03-30 12:08:51 +07:00
parent 81f6be6a00
commit 02cd1f5522
286 changed files with 23193 additions and 20055 deletions

View File

@ -1,6 +1,5 @@
#!/bin/bash
# NagLint PostToolUse hook — runs after Write/Edit on .ts/.tsx/.js/.jsx files
# Reads tool_use JSON from stdin, extracts file_path, runs nag --agent-json
# NagLint PostToolUse hook — runs after Write/Edit on source files
NAG=/Users/matsbosson/Documents/StayThree/NagLint/target/release/nag
@ -16,9 +15,8 @@ if [ -z "$FILE" ]; then
exit 0
fi
# Only lint source files — skip config, json, yaml, md, etc.
case "$FILE" in
*.ts|*.tsx|*.js|*.jsx) ;;
*.ts|*.tsx|*.js|*.jsx|*.mjs|*.cjs|*.mts|*.cts|*.rs) ;;
*) echo "{}"; exit 0 ;;
esac
@ -35,7 +33,6 @@ if [ "$RAW" = "{}" ] || [ -z "$RAW" ]; then
fi
# For .tsx/.jsx files: filter out AI-08 (logic density) — JSX is declarative
# and the 30% threshold doesn't apply to component files.
case "$FILE" in
*.tsx|*.jsx)
FILTERED=$(echo "$RAW" | python3 -c "

View File

@ -1,28 +1,2 @@
{
"hooks": {
"PreToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "jq -r '.tool_input.file_path // \"\"' | { read -r f; case \"$f\" in *.rs|*.js|*.jsx|*.mjs|*.cjs|*.ts|*.tsx|*.mts|*.cts) /Users/matsbosson/Documents/StayThree/NagLint/target/release/nag --rules-summary | jq -Rs '{hookSpecificOutput: {hookEventName: \"PreToolUse\", additionalContext: .}}';; *) echo '{}';; esac; }",
"statusMessage": "NagLint rules..."
}
]
}
],
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "/Users/matsbosson/Documents/StayThree/PettyUI/.claude/hooks/naglint.sh",
"statusMessage": "NagLint checking..."
}
]
}
]
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,455 +0,0 @@
Authors
- ![avatar](https://www.cress.codes/_next/image?url=%2Fstatic%2Fimages%2Fkale_jungle.jpg&w=96&q=75)NameDaniel CressTwitter
# Beyond Bespoke: How AI Turns Component Libraries Into Adaptive Systems
Every time we build a new feature, we follow the same ritual: design mockups, build custom components, wire up state management, test variations, deploy. Then the requirements change slightly, and we do it all again. A dashboard for managers needs different cards than one for employees. A mobile form needs different layouts than desktop. A beginner's view needs simpler options than an expert's.
We've gotten really good at building bespoke experiences. Maybe too good. We've optimized the process of creating unique interfaces for every context, every user type, every edge case. But what if we're solving the wrong problem?
What if instead of building better tools for creating variations, we built systems that generate variations on demand? What if our component libraries could adapt themselves based on context, using the same primitives we already have?
This isn't about replacing Nuxt UI or Shadcn or Radix. It's about teaching them to compose themselves.
## What We Built at Bambee
At Bambee, we built a system that generates contextual UI based on vast amounts of workplace data—performance metrics, compliance requirements, employee sentiment, turnover patterns, organizational health indicators. The challenge wasn't just showing data; it was surfacing the right insights with the right actions at the right time.
A manager dealing with high turnover needs different recommendations than one managing a stable team. An employee in their first month needs different guidance than a three-year veteran. A compliance alert for California employment law requires different visualizations than one for federal OSHA requirements.
We started down the familiar path: custom components for each scenario. But the combinatorial explosion quickly became clear. Dozens of card types. Countless variations of forms. Complex conditional logic everywhere.
So we tried something different.
### Dynamic Cards and Notices
The system analyzes company data and generates recommendations—we call them solutions and notices. Each one has different severity levels, different actions users can take, different visualizations to make the data clear.
One user might see a compliance alert with a bar chart showing policy gaps across departments. Another sees a performance insight with a timeline visualization of team productivity trends. A third sees a recognition opportunity with a simple progress indicator.
Here's the key: we're not building separate components for each type. We're using the same card component, the same underlying UI primitives from our component library. What changes is the structured data that drives them.
The system generates a schema-compliant payload that describes what to show, how to show it, and what actions should be available. The frontend trusts that structure and renders accordingly.
### Dynamic Wizards
The second use case was even more interesting: multi-step wizards that adapt to context.
The system detects information gaps—missing data that would improve recommendations. Based on what's missing and how critical it is, it generates a wizard to collect that information.
Sometimes it's a simple two-step survey: "What's your management philosophy? How large is your team?" Other times it's a comprehensive five-step wizard with conditional logic: if you answer yes to one question, you see follow-up questions; if you answer no, you skip to the next section.
The question types change dynamically. Sliders for rating scales. Checkbox groups for multiple selections. Matrices for comparing options across criteria. Date pickers for timelines. All generated from schemas, all rendered by the same form components.
We're not pre-building every possible wizard variation. We're defining the contract—what a wizard can contain, what question types are valid, how steps can be arranged—and letting the system compose variations on the fly.
## The Core Principles
Building this taught us some things about how to structure systems for dynamic UI generation. These aren't prescriptive rules, just patterns that worked for us.
### Schema as Contract
In traditional development, you design an interface, build the component, then wire it to data. The component defines what's possible.
In schema-driven development, you define the contract first. The schema is the source of truth. It describes what shapes of data are valid, what fields are required, what values are acceptable.
The backend generates data conforming to that schema. The frontend trusts the structure and renders accordingly. Neither side makes assumptions beyond what the schema guarantees.
This inverts the usual relationship. Instead of data conforming to components, components adapt to data (within the bounds of the schema).
A simple example:
```typescript
// Schema defines possibilities
ActionCardSchema = {
type: 'ACTION_CARD',
severity: 'LOW' | 'MEDIUM' | 'HIGH' | 'CRITICAL',
action: {
type: string,
label: string,
handler: { route: string, params: object }
},
visualization?: {
type: 'BAR' | 'LINE' | 'PIE',
data: object
}
}
// Backend generates instance based on context
const generatedCard = analyzeContext(userData)
// Frontend renders using component library primitives
<CardSlot data={generatedCard} />
```
The schema is doing a lot of work here. It's defining not just data types, but the vocabulary of what interfaces can express. Add a new visualization type to the schema, teach the frontend to render it, and suddenly all generated cards can use it.
### Type Safety Through Validation
The critical enabler for this approach is runtime validation. For us, that means Zod, but the principle applies to any schema validation library.
Here's why it matters: AI generates JSON. That JSON must match the exact structure your frontend expects. If it doesn't, you get runtime errors, broken UI, frustrated users.
With runtime validation, you create a feedback loop. AI generates output. You validate it immediately against your schema. If it fails validation, you send the errors back to the AI and ask it to regenerate.
The pattern looks like:
1. AI generates structured output based on context
2. Validate against schema immediately
3. If invalid → capture specific validation errors
4. Send those errors back to AI with original context
5. AI regenerates, accounting for what went wrong
6. Retry with exponential backoff (2-3 attempts max)
7. If all retries fail → fallback to safe default
This creates remarkably reliable output. The AI learns your schema requirements through the error messages. After a few iterations of improving prompts and tightening schemas, validation failures become rare.
The validation itself is straightforward:
```typescript
try {
const validated = CardSchema.parse(aiOutput)
return validated
} catch (error) {
await retryWithRefinement(aiOutput, error)
}
```
But the implications are profound. Your frontend never sees invalid data. Type safety is enforced at runtime. Breaking changes to schemas are caught immediately, not in production.
### Slots Over Specifics
We stopped building `<ComplianceCard>`, `<PerformanceCard>`, `<OnboardingCard>`. Instead, we built `<ActionSlot>` that accepts structured data and routes to appropriate presentation.
The slot examines the schema, determines which component primitives to use, and composes the final UI.
A compliance alert might render as: red badge with alert icon, bar chart visualization, "Review Policy" button that routes to policy creation. A performance insight might render as: blue badge with trend icon, line chart visualization, "View Details" button that opens a details modal.
Same slot. Same underlying Button, Card, Badge, and Chart components from our component library. Different compositions based on the schema data.
This is more than just abstraction. It's a different mental model. You're not building components for specific use cases. You're building a rendering engine that interprets schemas and composes UI from primitives.
The power comes from the mapping layer. It reads the schema and makes decisions:
- What badge color and icon represent this severity level?
- Which chart component matches this visualization type?
- What button variant and text match this action type?
- How should these elements be arranged for this context?
As you add more schema types, the mapping layer grows. But the underlying components stay the same.
### Visualization as Configuration
Instead of building separate chart components for every context, we treat visualizations as configuration.
The backend sends structured data describing what to visualize and how:
```js
{
visualizationType: 'BAR',
data: {
labels: ['Q1', 'Q2', 'Q3', 'Q4'],
values: [23, 45, 67, 89]
},
theme: 'minimal',
options: { showLegend: false }
}
```
The frontend has a factory function. It looks at the visualization type and says, "BAR chart? Render the BarChart component from our library with these props."
This means adding a new visualization type is a small change. Add the type to your schema, teach the factory to handle it, and now every part of your system that generates visualizations can use it.
We extended this with a vector database of illustrations. The system can describe the visual context it needs—"performance improvement scenario with upward trend"—then query embeddings to find the closest matching illustration from our library. No manual asset selection. Just semantic matching between generated context and available visuals.
### Dynamic Wizards from Schemas
Multi-step forms become declarative data structures.
A wizard schema defines:
- How many steps
- What questions appear in each step
- Question types and validation rules
- Conditional display logic
- Progress indicators and navigation
The frontend loops through the schema and renders appropriate input components. A question with type `'SLIDER'` renders your library's slider component. A question with type `'CHECKBOX'` renders a checkbox group.
The power is in the conditional logic. Questions can show or hide based on previous answers. Entire steps can be skipped based on user context. Validation rules can reference other questions.
All described in the schema. All rendered by generic form components.
Adding a new question type means updating the schema to include it and teaching one component to render it. Not rebuilding every wizard that might use it.
This is where it clicked for me: we weren't building forms anymore. We were building a form generation engine.
## The Future Vision
This is where it gets interesting. What we built at Bambee is a proof of concept. It works in production, handles real complexity, serves real users. But it's just scratching the surface of what's possible.
Let me paint some pictures of where this could go. To illustrate these concepts without diving into proprietary specifics, I'll use a hypothetical recipe and meal planning platform as a concrete example—but these patterns apply across any domain with similar variability.
### Pages That Generate Themselves
Imagine you're building a recipe and meal planning platform. A user opens the app and says, "I want to plan meals for the week."
The system analyzes their context:
- Family size and ages
- Dietary restrictions and preferences
- Available cooking time
- Current pantry inventory
- Cooking skill level
- Budget constraints
- Past meal preferences
From this, it generates a complete page schema. Not a page that exists in your codebase. A page composed on the fly:
A three-column dashboard layout. Left column shows a weekly calendar with meal slots, each slot showing prep time and ingredient overlap with other meals. Center column shows a shopping list organized by grocery store section, with cost estimates and substitution suggestions. Right column shows a nutrition summary chart aggregating the week's macros, plus a comparison to their goals.
At the bottom, an action row with context-aware buttons: export meal plan to calendar, send shopping list to grocery app, adjust for budget, regenerate for more variety.
The frontend receives this schema and composes it using your component library's Grid, Card, Calendar, List, Chart, and Button components. The page never existed before this moment. It was assembled because this specific user, in this specific context, needed this specific combination of features.
That's the radical shift: from pages as files in a repository to pages as generated compositions. The repository contains the components and the schemas. The combinations emerge from context.
### Context-Aware Form Adaptation
Same underlying data, completely different form based on who's using it.
A beginner user gets a simplified recipe entry form: basic fields, lots of help text, suggested defaults, links to video tutorials explaining cooking terms. Single-column layout, large touch targets, progress saved after every field.
An advanced user gets the compact version: all fields visible, technical terminology, advanced options like ingredient ratios and technique variations, keyboard shortcuts for quick entry. Multi-column layout, minimal explanatory text, batch editing capabilities.
The system decides which variation to show based on user behavior analysis. Not A/B testing. Not user segments. Individual adaptation.
And it goes deeper. Forms that adapt mid-flow based on answers. User selects "dietary restriction: vegan" → the form immediately hides all questions about meat preparation, adds questions about B12 supplementation, adjusts the nutrition target ranges, suggests vegan protein sources in the ingredient picker.
The form is responding to context in real-time, reshaping itself to show what's relevant and hide what isn't.
### Adaptive Complexity
Interfaces that scale complexity based on user sophistication, not just hiding advanced features behind a settings toggle.
A recipe platform might show the same dish completely differently based on skill level. Beginners see: "Sauté the onions until soft" with a photo showing what "soft" looks like and a link to a basic sautéing video. Intermediate cooks see: "Sauté onions in butter over medium heat until translucent, 5-7 minutes" with suggested pan types and heat settings. Advanced cooks see: "Sweat onions in clarified butter, 82°C, until cell walls break down but no Maillard reaction occurs—monitor for steam release, not browning" with technique alternatives like using a immersion circulator for precise temperature control.
The ingredient list adapts too. Beginners see standard grocery store items. Intermediate cooks see preferred brands and substitution options. Advanced users see specific varieties ("Vidalia onions for sweetness, or shallots for depth"), quality indicators, and even molecular composition notes for technique-critical ingredients.
Same recipe. Same underlying data. But the interface reveals layers of technical sophistication progressively, matching what each user can handle and wants to see.
The system watches behavior. User consistently completes advanced recipes without issues? Start showing more complexity. User struggles with intermediate recipes? Pull back to basics.
This isn't just hiding fields or showing tooltips. It's fundamentally different interfaces generated for different capability levels, all from the same underlying schemas.
### Cross-Domain Schema Standards
Here's where my mind goes to interesting places.
What if schema patterns became standardized across domains?
An `ACTION_CARD` schema could power meal suggestions in a recipe app, workout recommendations in a fitness app, budget alerts in a finance app, treatment plan updates in a healthcare app. Different domains, same structure: context analysis → recommended action → visualization → user choice.
A `WIZARD` schema could power dietary preference surveys in a recipe app, goal-setting wizards in a fitness app, budget creation flows in a finance app, symptom checkers in a healthcare app. Same multi-step structure, same question types, same conditional logic patterns.
Your component library becomes a universal renderer for structured intents. You build the primitives once—cards, forms, charts, buttons—and they work across every domain that speaks the schema language.
This is bigger than code reuse. It's conceptual reuse. The patterns for how to structure adaptive UI become portable knowledge, not locked into specific implementations.
Imagine open schema standards for common UI patterns. Like how we have standard HTTP methods or standard database query languages, we could have standard schemas for "action recommendation with visualization" or "multi-step data collection with conditional logic."
Build a great implementation once, use it everywhere.
### The Self-Assembling Application
Push this to its logical endpoint: applications that materialize from intent.
You describe what you want in natural language: "I want to help users plan healthy meals on a budget with easy recipes they can actually make."
The AI generates schemas for:
- Data models (user profiles, recipes, pantry items, meal plans)
- UI patterns (dashboard layouts, recipe cards, planning wizards)
- Action types (save recipe, generate shopping list, track spending, suggest substitutions)
- Visualizations (nutrition charts, budget tracking, ingredient freshness timelines)
You review the schemas, refine them, approve them. The frontend already knows how to render any valid schema. The backend already knows how to validate and store schema-conforming data.
What you're doing is no longer building features. You're curating and refining schemas. The system does the composition.
This sounds far-fetched until you realize we're already doing pieces of it. Code generation tools are getting better. Schema validation is mature. Component libraries are comprehensive. AI understands context remarkably well.
We're just connecting the pieces.
### Personalization Without Fragmentation
Traditional personalization means building variants: "Here's the health-focused version. Here's the budget-focused version. Here's the time-saving version."
You end up managing three codebases pretending to be one. Changes require updating all variants. Testing multiplies. Maintenance becomes painful.
Schema-driven personalization means generating the optimal interface for each user from the same underlying system.
User A cares about health. Their recipe cards prominently display nutrition information, macro breakdowns, ingredient quality scores, health impact summaries. The charts show nutrient density. The recommendations prioritize nutritional completeness.
User B cares about time. Their recipe cards show prep time first, active vs. passive time breakdowns, make-ahead options, batch cooking opportunities. The charts show time saved through meal prep. The recommendations prioritize efficiency.
User C cares about budget. Their recipe cards show cost per serving, bulk buying opportunities, seasonal ingredient savings, leftover utilization. The charts show cost comparisons. The recommendations prioritize affordability.
Same data. Same component library. Same schemas. Infinitely variable presentation based on what matters to each individual user.
No variant management. No A/B test fragments. No "which version am I in?" confusion. Just contextual generation.
And because it's schema-driven, you can combine dimensions. User D cares about both health and budget. They get nutrition data weighted by cost-effectiveness. User E cares about time and is also vegan. They get quick recipes that happen to be plant-based, not "vegan recipes" as a special category.
The combinations emerge from the generation logic, not from pre-built variants.
## The Trade-offs
This sounds exciting, and it is. But it's not free. The complexity moves around; it doesn't disappear.
### Schema Management Becomes Critical
Your schemas are your contract. They're what enables the whole system to work. Which means changing them is like changing your API.
You need versioning strategies. How do you evolve schemas without breaking existing data? How do you migrate old schema instances to new versions? How do you deprecate schema fields while maintaining backward compatibility?
You need synchronization mechanisms. The backend that generates schemas and the frontend that renders them must stay aligned. A mismatch means broken UI or validation errors.
You need discovery tools. As schemas multiply, developers need ways to find them, understand them, know which one to use for which scenario. Documentation becomes more important, not less.
This is real work. You might need a schema registry, similar to what you'd use for event-driven architectures. You might need automated testing that validates schema compatibility across versions. You might need tooling to generate TypeScript types from schemas and keep them in sync. Tools like Storybook become invaluable—you can document not just components, but how those components render different schema variations. Each story becomes a living example of "here's this schema shape, here's how it renders," making it easier for developers to understand which schema to use for which scenario.
Schema management is now a first-class concern in your architecture.
### Database Structure Gets Messy
The relational database purist in you will not like this.
Instead of neat columns with proper foreign keys and database-level constraints, you end up with JSONB blobs. The validation that would normally happen at the database layer moves to application code. Querying becomes harder—you can't easily "show me all high-priority compliance actions" when that data is buried in JSON.
There are workarounds. Hybrid models where you extract commonly-queried fields to columns and keep the dynamic data as JSON. PostgreSQL's JSONB type with indexes on frequently-accessed paths. Generated columns that pull specific values out of JSON for querying. Materialized views for complex queries that need to run often.
But it's more complex than traditional relational design. You're trading structure for flexibility.
The database is no longer your source of truth for what data is valid. Your schemas are. The database is just storage.
This is a philosophical shift as much as a technical one.
### Debugging Becomes Archaeology
When something renders wrong, you can't just look at a component file and see what's broken.
You need to trace: What was the user's context? What did the AI decide based on that context? What schema did it generate? Did it pass validation? How did the mapping layer interpret it? Which component got selected? What props were passed?
That's a lot of layers between "user saw wrong thing" and "here's the bug."
You need comprehensive logging at each step. You need replay tools that can take a saved context and schema and show you exactly how it rendered. You need schema versioning so you know which version of which schema generated which UI at which time. You need visibility into AI decision-making—why did it choose this action type over that one?
Debugging a static component is straightforward. Debugging a generated interface is detective work.
The tooling helps, but the conceptual overhead is real.
### Testing Strategy Becomes Critical
Testing dynamically generated UIs requires a different approach than testing traditional components. You can't just write unit tests for a component and call it done—the component itself might be simple, but the schema that drives it can vary infinitely.
Our testing strategy has three layers:
**Schema validation tests** are the foundation. Every schema gets comprehensive validation tests that verify the structure itself is correct. These tests catch issues like missing required fields, invalid enum values, or type mismatches. We treat schemas as first-class code artifacts with their own test suites.
**Contract tests** verify the relationship between schemas and components. Given a valid schema, does the component render without errors? We maintain a library of example schemas—edge cases, common patterns, minimal valid schemas—and run them through the rendering pipeline. This catches breaking changes when either schemas or components evolve.
**Integration tests** validate the full generation pipeline. We mock the AI responses with known schemas, then verify the entire flow: context analysis → schema generation → validation → rendering. This ensures the retry logic works, fallbacks activate correctly, and error boundaries catch failures gracefully.
We rely heavily on snapshot testing for the rendered output. When a schema changes, snapshot tests highlight exactly what UI changes resulted. This gives us confidence that schema evolution doesn't break existing interfaces unexpectedly.
The biggest shift is acceptance that you can't test every possible variation. Instead, you test the boundaries: minimum valid schemas, maximum complexity schemas, common patterns, and known edge cases. Type safety through Zod catches most issues at compile time, and runtime validation catches the rest before users see them.
### Error Handling Multiplies
More moving parts means more things that can go wrong.
AI generation can fail. Network timeout, rate limit, malformed response. Validation can fail. AI generated something that doesn't match the schema. Retry loop can exhaust. After three attempts, still no valid schema. Rendering can fail. Unknown schema type, missing required data, component error.
You need graceful fallbacks for every failure mode.
Default schemas that are safe to show when generation fails. Error boundaries in the frontend that catch rendering failures and show something useful. Monitoring and alerting for validation failures so you know when your prompts or schemas need adjustment. User-friendly error states—"We're having trouble generating recommendations right now, here's what we suggest generally..."
Every dynamic system trades simplicity for resilience engineering.
### Performance Considerations
AI generation adds latency. LLM API calls can take seconds. You can't generate fresh schemas on every page load.
You need strategies: pre-generation for common scenarios, caching generated schemas by context hash, background generation with optimistic UI, hybrid approaches where you show static defaults while dynamic enhancement loads, edge computing to move generation closer to users.
But you're adding complexity to maintain responsiveness. The simple "render component with props" is now "check cache, maybe generate, validate, render, handle failure cases."
The performance budget gets spent differently.
### Team Learning Curve
This is a different way of thinking about UI development.
Your team needs to understand schema design—what makes a good schema, how to evolve them, how to version them. They need to understand runtime validation and how to write schemas that AI can reliably generate. They need to understand slot-based architecture and mapping layers. They need to understand the trade-offs between flexibility and predictability.
Not every team wants this complexity. For simple, predictable applications, it's overkill. The traditional component-per-feature approach works fine when features are truly unique and don't follow patterns.
You're choosing a different set of problems. More upfront design work on schemas, less repetitive component building. More system-level thinking, less feature-level implementation.
It's not objectively better. It's different. And it requires buy-in.
## When Does This Make Sense?
So when should you actually consider this approach?
**Good fit:**
You're building something with high variability in user contexts or data types. The same underlying features need to look different for different users, different roles, different situations.
You have frequent new requirements that follow similar patterns. Not "build a completely new feature," but "this feature needs to work slightly differently for this new context."
Your domain is one where AI can make intelligent contextual decisions. There are patterns to learn, data to analyze, reasonable inferences to make.
Your team is comfortable with schema-driven development. Or willing to learn. And has the capacity to manage the additional architectural complexity.
Your users benefit meaningfully from personalized, adaptive experiences. The variability actually matters to them; it's not just engineer preference.
**Bad fit:**
You have pixel-perfect design requirements. Brand campaigns, marketing sites, anything where the exact visual presentation is non-negotiable. Schema-driven generation gives you flexibility, not precision.
Your feature set is predictable and stable. If you're building ten truly unique features with no pattern between them, there's no schema to extract. Just build the ten features.
You have a small team without capacity for schema management. The overhead might outweigh the benefits.
You're working on performance-critical real-time interactions. The latency of generation and validation might not be acceptable.
You have regulatory requirements for fixed UI flows. Sometimes the law requires specific workflows in specific orders. Dynamic generation adds compliance risk.
**The litmus test:** If you find yourself building slight variations of the same component over and over—same structure, different data, different actions, different styling—you're a candidate. You have patterns worth extracting into schemas.
If every feature is truly unique, truly bespoke, you're probably not.
## Components Learning to Think
Component libraries aren't dead. They're evolving.
We're moving from tools we manually compose to systems that compose themselves based on context.
What we built at Bambee is proof that this works in production. Dynamic wizards that adapt to what information is missing. Adaptive cards that surface different insights for different users. Contextual visualizations that show what matters most. All using our component library—Nuxt UI—just arranged by the system instead of by developers.
But it's early. The schemas are still simple. The generation logic is straightforward. The mapping layers are manageable.
What comes next is more interesting.
More sophisticated schemas that can express complex layouts, responsive variations, accessibility requirements, animation preferences. Cross-domain schema standards that let us share UI patterns across completely different applications. Entire pages assembled from intent rather than files. Vector-powered asset selection that makes every interface feel custom. Progressive complexity that adapts to user capability in real-time. Mass personalization without the maintenance burden of variants.
The vision: developers define schemas and provide component libraries. AI generates optimal UI for each user's context. Users see interfaces that feel custom-built for them. No custom building required.
This isn't replacing the craft of UI development. It's augmenting it. Moving us from pixel-pushing to pattern-defining. From building variations to building systems that generate variations. From asking "how do I build this interface?" to asking "how do I describe the space of possible interfaces?"
The future of UI development might not be about building interfaces. It might be about building the systems that build interfaces.
Component libraries aren't dying. They're learning to think.

View File

@ -1,244 +0,0 @@
Share
- [Share on Facebook](https://www.facebook.com/sharer.php?u=https%3A%2F%2Fbyteiota.com%2Fvite-8-0-rolldown-migration-guide-10-30x-faster-builds%2F "Share on Facebook")
- [Share on Twitter](https://twitter.com/share?url=https%3A%2F%2Fbyteiota.com%2Fvite-8-0-rolldown-migration-guide-10-30x-faster-builds%2F&text=Vite%208.0%20Rolldown%20Migration%20Guide:%2010-30x%20Faster%20Builds "Share on Twitter")
- [Share on Linkedin](https://www.linkedin.com/shareArticle?mini=true&url=https%3A%2F%2Fbyteiota.com%2Fvite-8-0-rolldown-migration-guide-10-30x-faster-builds%2F "Share on Linkedin")
Vite 8.0 stable dropped on March 12, 2026, replacing its dual-bundler architecture with [Rolldown](https://rolldown.rs/), a single Rust-based bundler delivering 10-30x faster builds. Linear saw production builds shrink from 46 seconds to 6 seconds—an 87% reduction. With Vite downloaded 65 million times weekly, this upgrade affects millions of developers. Heres your migration guide.
## Vite 8 Unifies Build Pipeline with Rolldown
Vite 8 consolidates two bundlers into one. Previously, Vite used esbuild for fast development and Rollup for optimized production builds. This dual-bundler approach worked but created potential inconsistencies between dev and prod environments. Rolldown eliminates that split.
Rolldown is a Rust-based bundler with full Rollup API compatibility. It matches esbuilds development speed while delivering 10-30x faster production builds than Rollup. In official benchmarks testing 19,000 modules, Rolldown completed in 1.61 seconds versus Rollups 40.10 seconds—a 25x improvement.
The architectural unification simplifies configuration. Developers no longer juggle separate esbuild and rollupOptions settings. One bundler, one config, consistent behavior across environments.
## 10-30x Faster Builds in Real-World Projects
Performance gains are substantial. [Linear reduced production build times by 87%](https://vite.dev/blog/announcing-vite8), dropping from 46 seconds to 6 seconds. Beehiivs large codebase saw 64% improvement. Mercedes-Benz.io cut build times by 38%.
However, performance gains scale with project size. Small projects under 100 modules see 2-5x improvements. Mid-sized projects between 100-500 modules hit 5-10x. Large projects with 500+ modules achieve the advertised 10-30x gains.
One developer testing a single-page app watched builds shrink from 3.8 seconds to 0.8 seconds—a clean 5x improvement. For large enterprise apps, these savings multiply across hundreds of daily builds, cutting hours from CI/CD pipelines.
## How to Migrate to Vite 8
Migration is straightforward for most projects. Update Vite to 8.0, test locally, deploy if no issues arise. A compatibility layer auto-converts esbuild and rollupOptions configurations to Rolldown equivalents.
Basic migration:
```bash
# Update to Vite 8.0
npm install vite@8
# Test development server
npm run dev
# Test production build
npm run build
# Benchmark performance
time npm run build
```
Most projects work without configuration changes. The compatibility layer handles the transition automatically. If you encounter issues, [Vite provides a gradual migration path](https://main.vite.dev/guide/migration): test with rolldown-vite on Vite 7 first, then upgrade to Vite 8 stable. This two-step approach isolates Rolldown-specific problems from general upgrade issues.
## Should You Upgrade? Decision Framework
Upgrade priority depends on project size and build frequency. Large codebases with 500+ modules benefit most—10-30x gains justify immediate migration. Teams running multiple builds daily see compounding time savings. If CI/CD pipelines take 40 seconds per build, Rolldown cuts that to 2 seconds, saving 38 seconds × 100 builds = 63 minutes daily.
Mid-sized projects between 100-500 modules should upgrade within the month. Youll see 5-10x improvements—noticeable but not game-changing. Standard release cycles (daily or weekly deploys) make this a medium priority.
Small projects under 100 modules see 2-5x gains. Still worthwhile, but less impactful. If youre risk-averse or running mission-critical production apps, waiting 1-2 months for community feedback is reasonable. Let others find the edge cases first.
Skip immediate upgrade if you rely on obscure Rollup plugins that may lack Rolldown compatibility. Check the Vite plugin registry first. Also skip if youre on Vite 6 or older—address that gap before jumping to Vite 8.
## Troubleshooting Common Migration Issues
Three issues account for most migration headaches: CommonJS interop changes, manualChunks deprecation, and esbuild transform failures.
CommonJS imports may break due to Rolldowns stricter module handling. If runtime errors appear when importing CJS modules, add `legacy.inconsistentCjsInterop: true` to your config temporarily. Long-term, migrate to ESM or fix module resolution. This isnt Vites fault—its exposing existing module system inconsistencies.
The `manualChunks` config no longer works. Vite 8 uses `codeSplitting` instead, offering more granular control:
```javascript
// OLD (Vite 7)
export default defineConfig({
build: {
rollupOptions: {
output: {
manualChunks(id) {
if (/\/react(?:-dom)?/.test(id)) {
return 'vendor'
}
}
}
}
}
})
// NEW (Vite 8)
export default defineConfig({
build: {
rolldownOptions: {
output: {
codeSplitting: {
groups: [\
{ name: 'vendor', test: /\/react(?:-dom)?/ }\
]
}
}
}
}
})
```
Plugins using `transformWithEsbuild` will fail because esbuild is no longer bundled. Migrate to `transformWithOxc` or install esbuild manually as a peer dependency. The @vitejs/plugin-react v6 already made this switch, using Oxc for React Refresh transforms and eliminating the Babel dependency entirely.
## Whats Next for Vite
Vites roadmap includes Full Bundle Mode (experimental), which serves bundled files in development for 3× faster dev server startup, 40% faster full reloads, and 10× fewer network requests. This addresses one of Vites last pain points—hundreds of separate module requests in large apps.
[VoidZero](https://voidzero.dev/posts/announcing-rolldown-rc), the team behind Vite and Rolldown, is building a unified Rust toolchain for JavaScript development. Rolldown handles bundling, Oxc powers compilation and minification, and more tools are coming. The trend is clear: Rust-based tooling is replacing JavaScript-based build tools across the ecosystem, from swc to turbopack to Biome.
## Key Takeaways
- Vite 8.0 stable replaces esbuild + Rollup with a single Rust bundler (Rolldown), delivering 10-30x faster production builds while maintaining plugin compatibility
- Large projects (500+ modules) see the biggest gains (10-30x), mid-sized projects hit 5-10x, small projects get 2-5x—performance scales with codebase size
- Most projects migrate without config changes thanks to auto-conversion, but CommonJS interop and manualChunks require manual fixes
- Upgrade now if youre on Vite 7 with large codebases or frequent builds—the stable release is production-ready and compatibility is high
- Future Vite improvements (Full Bundle Mode, Rust toolchain expansion) show continued innovation, making this a safe long-term bet
Vite 8 isnt just faster—its simpler. One bundler, one config, one mental model. The dual-bundler era is over.
### Share
![ByteBot](<Base64-Image-Removed>)
[ByteBot](https://byteiota.com/author/bytebot/ "Posts by ByteBot")
I am a playful and cute mascot inspired by computer programming. I have a rectangular body with a smiling face and buttons for eyes. My mission is to cover latest tech news, controversies, and summarizing them into byte-sized and easily digestible information.
[Previous](https://byteiota.com/bitnet-tutorial-run-100b-llms-on-cpu-with-1-bit-inference/)
### [BitNet Tutorial: Run 100B LLMs on CPU with 1-Bit Inference](https://byteiota.com/bitnet-tutorial-run-100b-llms-on-cpu-with-1-bit-inference/)
[Next](https://byteiota.com/openclaw-china-ban-first-ai-agent-crackdown/)
### [OpenClaw China Ban: First AI Agent Crackdown](https://byteiota.com/openclaw-china-ban-first-ai-agent-crackdown/)
#### You may also like
### [Reddit Bot Verification: 100K Daily Removals Drive Crackdown](https://byteiota.com/reddit-bot-verification-100k-daily-removals-drive-crackdown/)
2 hours ago
### [AMD Ryzen 9 9950X3D2: First Dual-Cache CPU Hits 208MB](https://byteiota.com/amd-ryzen-9-9950x3d2-first-dual-cache-cpu-hits-208mb/)
4 hours ago
[![](<Base64-Image-Removed>)](https://byteiota.com/cloud-waste-2026-235b-lost-to-idle-resources/)
### [Cloud Waste 2026: $235B Lost to Idle Resources](https://byteiota.com/cloud-waste-2026-235b-lost-to-idle-resources/)
5 hours ago
[![](<Base64-Image-Removed>)](https://byteiota.com/lg-1hz-display-pushes-dell-xps-16-to-27-hour-battery-life/)
### [LG 1Hz Display Pushes Dell XPS 16 to 27-Hour Battery Life](https://byteiota.com/lg-1hz-display-pushes-dell-xps-16-to-27-hour-battery-life/)
6 hours ago
### [macOS 26 Consistently Bad: Its Design, Not Bugs](https://byteiota.com/macos-26-consistently-bad-its-design-not-bugs/)
8 hours ago
[![](<Base64-Image-Removed>)](https://byteiota.com/cursor-composer-2-10x-cheaper-than-claude-beats-opus-4-6/)
### [Cursor Composer 2: 10x Cheaper Than Claude, Beats Opus 4.6](https://byteiota.com/cursor-composer-2-10x-cheaper-than-claude-beats-opus-4-6/)
9 hours ago
### Leave a reply [Cancel reply](https://byteiota.com/vite-8-0-rolldown-migration-guide-10-30x-faster-builds/\#respond)
Your email address will not be published.Required fields are marked \*
Comment
Name \*
Email \*
Website
Save my name, email, and website in this browser for the next time I comment.
Δ
#### More in: [JavaScript](https://byteiota.com/programming/javascript/)
[![Featured image for HTMX Tutorial 2026: Replace React with 14KB HTML](<Base64-Image-Removed>)](https://byteiota.com/htmx-tutorial-2026-replace-react-with-14kb-html/)
### [HTMX Tutorial 2026: Replace React with 14KB HTML](https://byteiota.com/htmx-tutorial-2026-replace-react-with-14kb-html/)
5 days ago
[![Featured image for TanStack Start: Type-Safe React Framework for 2026](<Base64-Image-Removed>)](https://byteiota.com/tanstack-start-type-safe-react-framework-for-2026/)
### [TanStack Start: Type-Safe React Framework for 2026](https://byteiota.com/tanstack-start-type-safe-react-framework-for-2026/)
5 days ago
[![Featured image for Hono Framework: Build Edge APIs on Cloudflare Workers](<Base64-Image-Removed>)](https://byteiota.com/hono-framework-build-edge-apis-on-cloudflare-workers/)
### [Hono Framework: Build Edge APIs on Cloudflare Workers](https://byteiota.com/hono-framework-build-edge-apis-on-cloudflare-workers/)
6 days ago
[![Featured image for React Server Components: The Practical Guide for 2026](<Base64-Image-Removed>)](https://byteiota.com/react-server-components-the-practical-guide-for-2026/)
### [React Server Components: The Practical Guide for 2026](https://byteiota.com/react-server-components-the-practical-guide-for-2026/)
6 days ago
### [JavaScript Bloat: 3 Pillars Killing Bundle Size](https://byteiota.com/javascript-bloat-3-pillars-killing-bundle-size/)
6 days ago
### [Hono Framework: 14KB Edge API Alternative to Express](https://byteiota.com/hono-framework-14kb-edge-api-alternative-to-express/)
March 13, 2026
Next Article:
March 13, 2026
min read
-21 %
## [![logo](<Base64-Image-Removed>)](https://byteiota.com/)
[✕Close](https://byteiota.com/vite-8-0-rolldown-migration-guide-10-30x-faster-builds/#atbs-ceris-offcanvas-primary)
## [![logo](<Base64-Image-Removed>)](https://byteiota.com/)
[](https://byteiota.com/vite-8-0-rolldown-migration-guide-10-30x-faster-builds/#atbs-ceris-offcanvas-mobile)
## Latest Posts
### [Reddit Bot Verification: 100K Daily Removals Drive Crackdown](https://byteiota.com/reddit-bot-verification-100k-daily-removals-drive-crackdown/)
### [AMD Ryzen 9 9950X3D2: First Dual-Cache CPU Hits 208MB](https://byteiota.com/amd-ryzen-9-9950x3d2-first-dual-cache-cpu-hits-208mb/)
### [Cloud Waste 2026: $235B Lost to Idle Resources](https://byteiota.com/cloud-waste-2026-235b-lost-to-idle-resources/)
### [LG 1Hz Display Pushes Dell XPS 16 to 27-Hour Battery Life](https://byteiota.com/lg-1hz-display-pushes-dell-xps-16-to-27-hour-battery-life/)
### [macOS 26 Consistently Bad: Its Design, Not Bugs](https://byteiota.com/macos-26-consistently-bad-its-design-not-bugs/)
[![feedmatters.com](https://byteiota.com/wp-content/uploads/2026/02/promote-feedmatters-edited.png)](https://feedmatters.com/?utm_source=byteiota&utm_medium=banner&utm_campaign=popup)
×

View File

@ -1,112 +0,0 @@
# Dynamic Components
All primitive components that render a DOM element are dynamic, which means that you can modify the element or the component they should render as.
## Native elements [Section titled Native elements](https://corvu.dev/docs/dynamic-components/\#native-elements)
In most cases, you shouldnt need to change the DOM element that the primitive component renders. corvu has sensible defaults for all components. But there are cases where it makes sense to change them. An example would be the `Tooltip` trigger which renders as a `button` element. You may want to render a tooltip on a link (`a` tag) instead. To do this, you have to specify the `as` property on the trigger component:
```
<Tooltip.Trigger as="a" href="https://corvu.dev">
corvu.dev
</Tooltip.Trigger>
```
## Solid components [Section titled Solid components](https://corvu.dev/docs/dynamic-components/\#solid-components)
A much more common use case is to render a primitive component as a custom Solid component. This is useful to apply default styling or to add additional functionality.
For example, you might have your own, custom-styled button component and want to use it as a trigger for a dialog:
```
import {
ComponentProps,
splitProps,
} from 'solid-js'
import Dialog from '@corvu/dialog'
const CustomButton = (
props: ComponentProps<'button'> & { variant: 'fill' | 'outline' },
) => {
const [local, rest] = splitProps(props, ['variant'])
// Apply your custom styling here
return <button class={local.variant} {...rest} />
}
const DialogTrigger = () => (
<Dialog.Trigger as={CustomButton} variant="outline">
Open
</Dialog.Trigger>
)
```
Props not belonging to the primitive component will be passed through to your custom component. In this case, the `variant` prop is passed to the `CustomButton` component.
> ❗ To ensure functionality and accessibility, your component needs to spread the received props onto your element. Otherwise, corvu cant define props on the element and things will break.
## Component types [Section titled Component types](https://corvu.dev/docs/dynamic-components/\#component-types)
corvus dynamic components have a flexible type system. This allows library developers or users who want to create their own components based on corvus primitives to have a great developer experience.
Every dynamic component exposes 4 types.
For example, the `<Dialog.Trigger>` component exports the types `DialogTriggerCorvuProps`, `DialogTriggerSharedElementProps<T>`, `DialogTriggerElementProps` and `DialogTriggerProps<T>`.
Lets have a look at the types:
### CorvuProps [Section titled CorvuProps](https://corvu.dev/docs/dynamic-components/\#corvuprops)
`CorvuProps` contains all props that are specific to the primitive component and are not passed through to the rendered element. They are consumed by corvu. For example, the [`<Resizable.Handle />`](https://corvu.dev/docs/primitives/resizable/#Panel) has props like `minSize` or `collapsible`.
### SharedElementProps<T extends ValidComponent> [Section titled SharedElementProps<T extends ValidComponent>](https://corvu.dev/docs/dynamic-components/\#sharedelementpropst-extends-validcomponent)
`SharedElementProps` includes all props that get defined by corvu on the rendered element **but** can be overridden by the user. This usually includes the `ref` or `style` properties. The generic is used to properly type `ref` and event listeners.
### ElementProps [Section titled ElementProps](https://corvu.dev/docs/dynamic-components/\#elementprops)
`ElementProps` element props inherits all `SharedElementProps` and additionally includes all props that are set by corvu and cant be overridden by the user. This includes for example accessibility props like `aria-*`, `role` and `data-*`.
### Props<T extends ValidComponent> [Section titled Props<T extends ValidComponent>](https://corvu.dev/docs/dynamic-components/\#propst-extends-validcomponent)
This is the type that defines what props that corvu expects from the user. Its equal to `CorvuProps & Partial<SharedElementProps>`.
### DynamicProps [Section titled DynamicProps](https://corvu.dev/docs/dynamic-components/\#dynamicprops)
`DynamicProps` is a helper type that allows you to expose the dynamic aspect of corvu components from your custom component. Lets look at an example:
```
import {
type ValidComponent,
ComponentProps,
splitProps,
} from 'solid-js'
import Dialog, { type TriggerProps, type DynamicProps } from '@corvu/dialog'
// Define your custom props, including `TriggerProps` from corvu
export type CustomDisclosureTriggerProps<T extends ValidComponent = 'button'> = TriggerProps<T> & {
variant: 'fill' | 'outline'
}
// The generic `T` allows the user to specify
// the element this component should render as
const CustomDialogTrigger = <T extends ValidComponent = 'button'>(
props: DynamicProps<T, CustomDisclosureTriggerProps<T>>,
) => {
const [local, rest] = splitProps(props as CustomDisclosureTriggerProps, [\
'variant',\
])
// Define the default dynamic type, in this case 'button'.
// This can be overridden by the user.
return <Dialog.Trigger as="button" class={local.variant} {...rest} />
}
```
If you dont want to expose the dynamic aspect of corvus components, you can define the generic explicitly:
```
const CustomDialogTrigger = (
props: DynamicProps<'button', CustomDisclosureTriggerProps<'button'>>,
) => {
```
Developed and designed by [Jasmin](https://github.com/GiyoMoon/)

View File

@ -1,15 +0,0 @@
# Overview
## Primitives [Section titled Primitives](https://corvu.dev/docs/overview/\#primitives)
corvus growing list of UI primitives for SolidJS. Accessible, customizable and ready to use in your project!
[Accordion](https://corvu.dev/docs/primitives/accordion/) [Calendar](https://corvu.dev/docs/primitives/calendar/) [Dialog](https://corvu.dev/docs/primitives/dialog/) [Disclosure](https://corvu.dev/docs/primitives/disclosure/) [Drawer](https://corvu.dev/docs/primitives/drawer/) [OTP Field](https://corvu.dev/docs/primitives/otp-field/) [Popover](https://corvu.dev/docs/primitives/popover/) [Resizable](https://corvu.dev/docs/primitives/resizable/) [Tooltip](https://corvu.dev/docs/primitives/tooltip/)
## Utilities [Section titled Utilities](https://corvu.dev/docs/overview/\#utilities)
A set of commonly needed, low-level patterns that you might need in the modern, accessible web. They are exported as separate packages and can be used independently from corvu.
[dismissible](https://corvu.dev/docs/utilities/dismissible/) [focusTrap](https://corvu.dev/docs/utilities/focus-trap/) [list](https://corvu.dev/docs/utilities/list/) [persistent](https://corvu.dev/docs/utilities/persistent/) [presence](https://corvu.dev/docs/utilities/presence/) [preventScroll](https://corvu.dev/docs/utilities/prevent-scroll/) [transitionSize](https://corvu.dev/docs/utilities/transition-size/)
Developed and designed by [Jasmin](https://github.com/GiyoMoon/)

View File

@ -1,125 +0,0 @@
# State
By default, all corvu primitives manage their state internally. This means that they handle their state themselves and you dont need to pass any props/state to them.
corvu aims to be very customizable and provides various ways to control a primitive or access internal state. Lets take a look at them. Well use the [Dialog](https://corvu.dev/docs/primitives/dialog/) primitive as an example, but the same applies to all other primitives.
## Controlled State [Section titled Controlled State](https://corvu.dev/docs/state/\#controlled-state)
The easiest way to control a primitives state is by passing your own defined state to its `Root` component. Most of the time, this consists of a getter and setter property like in this example, where we control the open state of a dialog:
```
import Dialog from '@corvu/dialog'
import { createSignal } from 'solid-js'
const MyDialog = () => {
const [open, setOpen] = createSignal(false)
return (
<Dialog open={open()} onOpenChange={setOpen}>
<Dialog.Trigger>Open Dialog</Dialog.Trigger>
<Dialog.Content>
<Dialog.Label>Label</Dialog.Label>
</Dialog.Content>
</Dialog>
)
}
```
This allows you to use the open state anywhere in your code and alter it freely. The dialog will open and close accordingly.
## Context [Section titled Context](https://corvu.dev/docs/state/\#context)
Its also possible to access the context of every primitive. This allows you to get the internal state of a primitive from anywhere in your code, as long as its under the `Root` component, where the context gets provided. Accessing the context of a dialog looks like this:
```
import Dialog from '@corvu/dialog'
import { createSignal } from 'solid-js'
const DialogRoot = () => {
return (
<Dialog>
<DialogContent />
</Dialog>
)
}
const DialogContent = () => {
const { open, setOpen } = Dialog.useContext()
return (
<>
<p>The dialog is {open() ? 'open' : 'closed'}</p>
<button onClick={() => setOpen(open => !open)}>
My own, custom trigger button
</button>
<Dialog.Content>
<Dialog.Label>Label</Dialog.Label>
</Dialog.Content>
</>
)
}
```
Every primitive provides different properties in its context, have a look at the API section of each primitive to see whats available.
## Children callbacks [Section titled Children callbacks](https://corvu.dev/docs/state/\#children-callbacks)
The `Root` component of every primitive (and in a few cases others) also accepts a function as its children. By doing this, we can pass the internal state to the children for you to access. An example of this looks like this:
```
import Dialog from '@corvu/dialog'
import { createSignal } from 'solid-js'
const MyDialog = () => {
return (
<Dialog>
{(props) => (
<>
<p>The dialog is {props.open() ? 'open' : 'closed'}</p>
<button onClick={() => props.setOpen(open => !open)}>
My own, custom trigger button
</button>
<Dialog.Trigger>Open Dialog</Dialog.Trigger>
<Dialog.Content>
<Dialog.Label>Label</Dialog.Label>
</Dialog.Content>
</>
)}
</Dialog>
)
}
```
Note that the props passed from the `Root` component include reactive getters. Make sure to access them in a reactive scope, like you would in any other Solid component.
## Keyed context [Section titled Keyed context](https://corvu.dev/docs/state/\#keyed-context)
There may be situations where you want to use nested instances of the same primitive. For example, multiple dialogs with multiple trigger buttons that are hard to separate in the template. For this case, corvu allows you to pass a `contextId` to every primitive component to tell which context to use.
Heres how two nested dialogs would look like:
```
<Dialog contextId="dialog-1">
<Dialog contextId="dialog-2">
<Dialog.Trigger contextId="dialog-1">Open Dialog 1</Dialog.Trigger>
<Dialog.Trigger contextId="dialog-2">Open Dialog 2</Dialog.Trigger>
<Dialog.Content contextId="dialog-1">
<Dialog.Label contextId="dialog-1">Dialog 1</Dialog.Label>
</Dialog.Content>
<Dialog.Content contextId="dialog-2">
<Dialog.Label contextId="dialog-2">Dialog 2</Dialog.Label>
</Dialog.Content>
</Dialog>
</Dialog>
```
When using keyed contexts, you can pass the same key to the `useContext()` function to access the context of the respective primitive.
```
const { open, setOpen } = Dialog.useContext('dialog-1')
```
Developed and designed by [Jasmin](https://github.com/GiyoMoon/)

View File

@ -1,91 +0,0 @@
# Styling
corvu leaves the styling up to you. You can use Tailwind CSS, any CSS-in-JS library or just plain old CSS to style primitives.
## Data attributes [Section titled Data attributes](https://corvu.dev/docs/styling/\#data-attributes)
Components that can be in different states, e.g. `open` or `closed` for a dialog, provide data attributes to style them accordingly.
Here is an example of how to style a dialog based on its open state:
```
.dialog_content[data-open] {
/* styles to apply when open */
}
.dialog_content[data-closed] {
/* styles to apply when closed */
}
```
Dont forget to add the `dialog_content` class to your Dialog content component:
```
<Dialog.Content class="dialog_content">...</Dialog.Content>
```
Additionally, every corvu component has a data attribute for you to use. A dialog content element would render like this:
```
<div data-corvu-dialog-content data-open>...</div>
```
You can use it to style all components of the same kind at once:
```
[data-corvu-dialog-content] {
/* styles to apply to the dialog content */
}
[data-corvu-dialog-content][data-open] {
/* styles to apply when open */
}
[data-corvu-dialog-content][data-closed] {
/* styles to apply when closed */
}
```
Additionally, corvu provides plugins for these CSS frameworks:
- [Tailwind CSS plugin](https://corvu.dev/docs/installation/#tailwind-css-plugin)
- [UnoCSS preset](https://corvu.dev/docs/installation/#unocss-preset)
They make it easy to style components based on their current state using modifiers.
**Available modifiers**
- `corvu-open` -\> `&[data-open]`
- `corvu-closed` -\> `&[data-closed]`
- `corvu-expanded` -\> `&[data-expanded]`
- `corvu-collapsed` -\> `&[data-collapsed]`
- `corvu-transitioning` -\> `&[data-transitioning]`
- `corvu-opening` -\> `&[data-opening]`
- `corvu-closing` -\> `&[data-closing]`
- `corvu-snapping` -\> `&[data-snapping]`
- `corvu-resizing` -\> `&[data-resizing]`
- `corvu-disabled` -\> `&[data-disabled]`
- `corvu-active` -\> `&[data-active]`
- `corvu-dragging` -\> `&[data-dragging]`
- `corvu-selected` -\> `&[data-selected]`
- `corvu-today` -\> `&[data-today]`
- `corvu-range-start` -\> `&[data-range-start]`
- `corvu-range-end` -\> `&[data-range-end]`
- `corvu-in-range` -\> `&[data-in-range]`
- `corvu-side-top` -\> `&[data-side='top']`
- `corvu-side-right` -\> `&[data-side='right']`
- `corvu-side-bottom` -\> `&[data-side='bottom']`
- `corvu-side-left` -\> `&[data-side='left']`
These two CSS framework use similar syntax. You can style components like this:
```
<Dialog.Content
class="corvu-open:animate-in corvu-closed:animate-out"
>
...
</Dialog.Content>
```
## Animation [Section titled Animation](https://corvu.dev/docs/styling/\#animation)
corvu has built-in support for CSS animations and waits for any pending animation to finish before removing an element from the DOM. This means you can use CSS animations to animate the appearance and disappearance of primitives. Every unmountable component also provides a `forceMount` property which forces it to stay mounted in the DOM even when it is not visible. This is useful when using third-party animation libraries.
Developed and designed by [Jasmin](https://github.com/GiyoMoon/)

View File

@ -1,79 +0,0 @@
# CSS Anchor Positioning 2026: Practical Guide for Tooltips, Menus, and Smart Overlays
Positioning contextual UI has always been painful. Dropdowns, tooltips, and popovers often require JavaScript calculations, viewport checks, and custom collision logic. CSS Anchor Positioning moves a large part of this work into native styling.
## Why It Matters
- dropdown clipped on small screens
- tooltip detached from trigger on scroll
- menu appears off-screen in RTL or localized UI
## Core Mental Model
Define an anchor element
Attach floating UI to that anchor
Let CSS handle alignment behavior
## Real Use Cases
- action menu in data tables
- profile dropdown in sticky headers
- inline help tooltip in forms
- contextual edit controls in CMS
## UX Rules
- keep overlays close to trigger
- preserve keyboard focus flow
- add deterministic closing behavior
- avoid huge animation while repositioning
## Rollout Strategy
tooltip
dropdown
popover with richer content
## Conclusion
Anchor positioning is a structural improvement that reduces UI fragility in real products.
## Related posts
Continue reading on nearby topics.
[Latest CSS Gradient Features and Trends for 2026Latest CSS gradient features for 2026: new color combinations, mesh techniques, animated transitions, and practical production patterns.](https://css-zone.com/blog/css-gradient-trends-2026) [Core Web Vitals 2026: CSS Playbook for Faster LCP, Better INP, and Stable CLSA practical Core Web Vitals 2026 guide focused on CSS architecture, rendering strategy, font loading, and layout stability for real products.](https://css-zone.com/blog/core-web-vitals-2026-css-playbook) [CSS Best Practices for Real Projects: A Practical Playbook from CSS-Zone.comA practical CSS guide for production teams: architecture, naming, tokens, responsive strategy, performance, and accessibility. Includes many copy-paste-ready examples and workflows used on CSS-Zone.com.](https://css-zone.com/blog/css-best-practices-real-projects-css-zone) [Modern CSS Features You Should Use in 2026Explore the latest CSS features that are changing web development: container queries, :has() selector, cascade layers, and more cutting-edge techniques.](https://css-zone.com/blog/modern-css-features-2026)
## Comments
0
Sign in to leave a comment.
Sign in
No comments yet. Be the first.
Cookies
## We use cookies to keep things smooth
They help keep you signed in, remember your preferences, and measure what features land. You control what happens next.
[Privacy Policy](https://css-zone.com/privacy-policy) [Cookie rules](https://css-zone.com/cookie-policy)
Not nowAccept cookies
Contact
CSS file\_type\_scss
reCAPTCHA
Recaptcha requires verification.
[Privacy](https://www.google.com/intl/en/policies/privacy/) \- [Terms](https://www.google.com/intl/en/policies/terms/)
protected by **reCAPTCHA**
[Privacy](https://www.google.com/intl/en/policies/privacy/) \- [Terms](https://www.google.com/intl/en/policies/terms/)

View File

@ -1,382 +0,0 @@
[↓\\
Skip to main content](https://devproportal.com/frontend/react/mastering-headless-ui-radix-vs-react-aria/#main-content)
[DevPro Portal](https://devproportal.com/)
Table of Contents
Table of Contents
Its 2026. If you are still wrestling with `!important` overrides in Material UI or trying to hack the internal DOM structure of a Bootstrap component just to match a Figma design, youre doing it the hard way.
The React ecosystem has matured. Weve moved past the era of “All-in-One” component kits that dictate your styling. We are firmly in the era of **Headless UI**.
As senior developers and architects, our goal isnt just to put pixels on the screen; its to ship accessible, robust, and performant interfaces that scale. We want full control over the CSS (likely via Tailwind or CSS-in-JS) without reinventing the complex logic required for keyboard navigation, focus management, and screen reader support.
This article dives deep into the two heavyweights of the headless world: **Radix UI** and **React Aria**. Well compare them, build real components, and discuss the architectural implications of choosing one over the other.
## The Headless Architecture [\#](https://devproportal.com/frontend/react/mastering-headless-ui-radix-vs-react-aria/\#the-headless-architecture)
Before we touch the code, lets align on the mental model. “Headless” doesnt mean “no UI”; it means “unopinionated UI”.
In a traditional library (like AntD or MUI), the logic and the styling are coupled. In a Headless library, the library provides the **behavior** and **state**, while you provide the **rendering** and **styling**.
Here is how the data flow looks in a modern Headless setup:
Your Code
Headless Layer (Radix/Aria)
User Interaction
State Management
WAI-ARIA Roles
Focus Trap / Loops
Keyboard/Mouse Events
Tailwind / CSS Modules
JSX Structure
Framer Motion
Rendered DOM
### Why does this matter? [\#](https://devproportal.com/frontend/react/mastering-headless-ui-radix-vs-react-aria/\#why-does-this-matter)
1. **Accessibility (a11y) is hard:** Implementing a fully accessible Dropdown Menu takes weeks of testing across VoiceOver, NVDA, and JAWS. Headless libraries give you this for free.
2. **Design Freedom:** You own the `className`.
3. **Bundle Size:** You only import the logic you need.
* * *
## Prerequisites and Environment [\#](https://devproportal.com/frontend/react/mastering-headless-ui-radix-vs-react-aria/\#prerequisites-and-environment)
To follow along, ensure you have a modern React environment set up. We are assuming a 2026 standard stack:
- **Node.js:** v20+ (LTS)
- **React:** v19
- **Styling:** Tailwind CSS v4 (or v3.4+)
- **Icons:** Lucide React (optional but recommended)
### Setup [\#](https://devproportal.com/frontend/react/mastering-headless-ui-radix-vs-react-aria/\#setup)
Well create a lightweight sandbox.
Copy
```bash
# Create a Vite project
npm create vite@latest headless-demo -- --template react-ts
# Enter directory
cd headless-demo
# Install dependencies (We will use both for comparison)
npm install @radix-ui/react-popover @radix-ui/react-dialog react-aria-components class-variance-authority clsx tailwind-merge framer-motion
# Start dev server
npm run dev
```
_Note: We included `class-variance-authority` (CVA) and `tailwind-merge`. These are the bread and butter for handling styles in headless components._
* * *
## The Contenders: Radix vs. React Aria [\#](https://devproportal.com/frontend/react/mastering-headless-ui-radix-vs-react-aria/\#the-contenders-radix-vs-react-aria)
Choosing between these two is often the first architectural decision when building a Design System.
| Feature | Radix UI | React Aria (Adobe) |
| --- | --- | --- |
| **Philosophy** | Component-first. Provides unstyled primitives (e.g., `<Dialog.Root>`). | Hooks-first (historically), now offers Components. “Industrial Grade” a11y. |
| **API Surface** | simpler, cleaner JSX. Very “React-y”. | Extremely granular. Offers `useButton`, `useSelect`, etc., plus a new Component API. |
| **Styling** | Agnostic. Works perfectly with Tailwind. | Agnostic. The new `react-aria-components` has a specific `className` function for states (hover/focus). |
| **Animation** | Relies on external libs or CSS keyframes. Works great with Framer Motion. | Has built-in animation support in the new components API, but generally external. |
| **Mobile** | Good, but sometimes lacks nuanced touch interactions. | Best in class. Adobe tests on everything. Handles virtual keyboard quirks brilliantly. |
| **Bundle Size** | Modular. You install packages individually (e.g., `@radix-ui/react-tooltip`). | Modular, but the core logic is heavier due to extreme robustness. |
* * *
## Part 1: Building with Radix UI [\#](https://devproportal.com/frontend/react/mastering-headless-ui-radix-vs-react-aria/\#part-1-building-with-radix-ui)
Radix is generally the favorite for teams that want to move fast but maintain high quality. It powers the popular `shadcn/ui` collection.
Lets build a **Popover** component. This isnt just a tooltip; it needs to handle focus trapping (optional), closing on outside clicks, and keyboard formatting.
### The Implementation [\#](https://devproportal.com/frontend/react/mastering-headless-ui-radix-vs-react-aria/\#the-implementation)
We will use Tailwind for styling and `lucide-react` for an icon.
Copy
```tsx
// src/components/RadixPopover.tsx
import * as React from 'react';
import * as Popover from '@radix-ui/react-popover';
import { Settings2, X } from 'lucide-react';
import { clsx, type ClassValue } from 'clsx';
import { twMerge } from 'tailwind-merge';
// Utility for cleaner classes
function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}
export default function UserSettingsPopover() {
return (
<Popover.Root>
<Popover.Trigger asChild>
<button
className={cn(
"inline-flex items-center justify-center rounded-full w-10 h-10",
"bg-slate-100 text-slate-900 hover:bg-slate-200",
"focus:outline-none focus:ring-2 focus:ring-slate-400 focus:ring-offset-2",
"transition-colors duration-200"
)}
aria-label="Update dimensions"
>
<Settings2 size={20} />
</button>
</Popover.Trigger>
<Popover.Portal>
<Popover.Content
className={cn(
"rounded-lg p-5 w-[260px] bg-white shadow-[0_10px_38px_-10px_rgba(22,23,24,0.35),0_10px_20px_-15px_rgba(22,23,24,0.2)]",
"border border-slate-200",
"will-change-[transform,opacity]",
"data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95",
"data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95",
"side-top:slide-in-from-bottom-2 side-bottom:slide-in-from-top-2"
)}
sideOffset={5}
>
<div className="flex flex-col gap-2.5">
<p className="text-sm font-medium text-slate-900 mb-2">Dimensions</p>
<fieldset className="flex items-center gap-5">
<label className="text-xs text-slate-500 w-[75px]" htmlFor="width">Width</label>
<input
className="w-full h-8 px-2 text-xs border rounded text-slate-700 focus:ring-2 focus:ring-blue-500 outline-none"
id="width"
defaultValue="100%"
/>
</fieldset>
<fieldset className="flex items-center gap-5">
<label className="text-xs text-slate-500 w-[75px]" htmlFor="height">Height</label>
<input
className="w-full h-8 px-2 text-xs border rounded text-slate-700 focus:ring-2 focus:ring-blue-500 outline-none"
id="height"
defaultValue="25px"
/>
</fieldset>
</div>
<Popover.Close asChild>
<button
className="absolute top-3 right-3 w-6 h-6 inline-flex items-center justify-center rounded-full text-slate-500 hover:bg-slate-100 focus:shadow-[0_0_0_2px] focus:shadow-slate-400 outline-none"
aria-label="Close"
>
<X size={14} />
</button>
</Popover.Close>
<Popover.Arrow className="fill-white" />
</Popover.Content>
</Popover.Portal>
</Popover.Root>
);
}
```
### Analysis [\#](https://devproportal.com/frontend/react/mastering-headless-ui-radix-vs-react-aria/\#analysis)
1. **`asChild` Pattern:** Notice `<Popover.Trigger asChild>`. This is Radixs signature move. It merges the event handlers and ARIA attributes onto _your_ DOM node (`<button>`) instead of wrapping it in an unnecessary `<div>`.
2. **The Portal:**`<Popover.Portal>` automatically transports the content to `document.body`. This solves the classic `z-index` and `overflow: hidden` parent clipping issues.
3. **Data Attributes:** Styling is driven by `data-[state=open]`. This allows us to use pure CSS (or Tailwind utilities) for entry/exit animations without complex React state logic.
* * *
## Part 2: Building with React Aria Components (RAC) [\#](https://devproportal.com/frontend/react/mastering-headless-ui-radix-vs-react-aria/\#part-2-building-with-react-aria-components-rac)
React Aria used to be known for its low-level hooks (`useButton`, `useOverlay`), which were powerful but verbose. In late 2024/2026, the standard is **React Aria Components (RAC)**. These provide a component-based API similar to Radix but with Adobes rigorous “interaction modeling.”
Lets build a **Select (Dropdown)**. HTML `<select>` is notoriously hard to style.
### The Implementation [\#](https://devproportal.com/frontend/react/mastering-headless-ui-radix-vs-react-aria/\#the-implementation-1)
Copy
```tsx
// src/components/AriaSelect.tsx
import type { Key } from 'react-aria-components';
import {
Button,
Label,
ListBox,
ListBoxItem,
Popover,
Select,
SelectValue
} from 'react-aria-components';
import { ChevronDown } from 'lucide-react';
export default function AriaSelect() {
return (
<Select className="flex flex-col gap-1 w-[200px]">
<Label className="text-sm font-medium text-slate-700 ml-1">Favorite Framework</Label>
<Button className={({ isPressed, isFocusVisible }) => `
flex items-center justify-between w-full px-3 py-2
bg-white border rounded-lg shadow-sm text-left cursor-default
${isFocusVisible ? 'ring-2 ring-blue-500 border-blue-500 outline-none' : 'border-slate-300'}
${isPressed ? 'bg-slate-50' : ''}
`}>
<SelectValue className="text-sm text-slate-900 placeholder-shown:text-slate-400" />
<ChevronDown size={16} className="text-slate-500" />
</Button>
<Popover className={({ isEntering, isExiting }) => `
overflow-auto rounded-lg drop-shadow-lg border border-slate-200 bg-white
w-[var(--trigger-width)]
${isEntering ? 'animate-in fade-in zoom-in-95 duration-200' : ''}
${isExiting ? 'animate-out fade-out zoom-out-95 duration-200' : ''}
`}>
<ListBox className="p-1 outline-none">
{['React', 'Vue', 'Svelte', 'Angular', 'Qwik'].map((item) => (
<ListBoxItem
key={item}
id={item}
textValue={item}
className={({ isFocused, isSelected }) => `
cursor-default select-none rounded px-2 py-1.5 text-sm outline-none
${isFocused ? 'bg-blue-100 text-blue-900' : 'text-slate-700'}
${isSelected ? 'font-semibold' : ''}
`}
>
{({ isSelected }) => (
<div className="flex items-center justify-between">
<span>{item}</span>
{isSelected && <span></span>}
</div>
)}
</ListBoxItem>
))}
</ListBox>
</Popover>
</Select>
);
}
```
### Analysis [\#](https://devproportal.com/frontend/react/mastering-headless-ui-radix-vs-react-aria/\#analysis-1)
1. **Render Props for Styling:** RAC uses render props heavily for styling classes (e.g., `className={({ isFocused }) => ...}`). This exposes the internal interaction state directly to Tailwind. You dont need `data-` attributes; you have direct JS boolean access.
2. **Adaptive Behavior:** Adobe put incredible effort into mobile. On mobile devices, this component handles touch cancellation, scrolling behavior, and virtual keyboard avoidance better than almost any other library.
3. **Semantics:** The `<ListBox>` and `<ListBoxItem>` components ensure correct ARIA roles (`role="listbox"`, `role="option"`) are applied, which are different from a standard navigation menu.
* * *
## Performance and Common Pitfalls [\#](https://devproportal.com/frontend/react/mastering-headless-ui-radix-vs-react-aria/\#performance-and-common-pitfalls)
When implementing Headless UI in a production environment, watch out for these traps.
### 1\. The Bundle Size Myth [\#](https://devproportal.com/frontend/react/mastering-headless-ui-radix-vs-react-aria/\#1-the-bundle-size-myth)
You might think, “Im importing a huge library!” Not really. Both libraries support tree-shaking.
- **Radix:** You usually install specific packages (`@radix-ui/react-dialog`).
- **React Aria:** The monolithic package exports everything, but modern bundlers (Vite/Rollup/Webpack 5) shake out unused exports effectively.
However, React Aria IS logically heavier because it includes code for edge cases you didnt know existed (like specific screen reader bugs in older iOS versions).
### 2\. Focus Management [\#](https://devproportal.com/frontend/react/mastering-headless-ui-radix-vs-react-aria/\#2-focus-management)
The number one bug in custom modals is **Focus Trapping**.
- **Scenario:** User opens a modal. User hits `Tab`. Focus goes _behind_ the modal to the URL bar or the background content.
- **Solution:** Both libraries handle this, but you must ensure you dont accidentally unmount the component before the closing animation finishes. Radix handles this with `data-state` and animations, but manual conditional rendering (`{isOpen && <Dialog />}`) without `AnimatePresence` (if using Framer) can break the focus return feature.
### 3\. Z-Index Wars [\#](https://devproportal.com/frontend/react/mastering-headless-ui-radix-vs-react-aria/\#3-z-index-wars)
Both libraries use **Portals**.
- **Trap:** If your global CSS sets a high z-index on a sticky header, your ported modal might end up under it if the portal container isnt managed correctly.
- **Best Practice:** Create a dedicated stacking context or ensure your portal root (usually `body`) is handled correctly in your Tailwind config (`z-50` usually suffices for modals).
* * *
## Conclusion: Which one should you choose? [\#](https://devproportal.com/frontend/react/mastering-headless-ui-radix-vs-react-aria/\#conclusion-which-one-should-you-choose)
As an architect, your choice depends on your teams priorities.
**Choose Radix UI if:**
- You want a developer experience that feels “native” to React.
- You are heavily invested in the Tailwind ecosystem (it pairs beautifully).
- You need to ship fast and “Very Good” accessibility is acceptable.
- You are building a standard B2B SaaS dashboard.
**Choose React Aria if:**
- **Accessibility is non-negotiable.** (e.g., Government, Healthcare, Education).
- You need robust touch/mobile interactions (drag and drop, swipes).
- You prefer render-props for styling logic over CSS selectors.
- You are building a complex design system that needs to last 5+ years.
Both libraries represent the pinnacle of React component development in 2026. By separating behavior from design, they allow us to build UIs that are unique to our brand but universal in their usability.
### Further Reading [\#](https://devproportal.com/frontend/react/mastering-headless-ui-radix-vs-react-aria/\#further-reading)
- [WAI-ARIA Authoring Practices Guide (APG)](https://www.w3.org/WAI/ARIA/apg/) \- The bible for accessible patterns.
- [Radix UI Docs](https://www.radix-ui.com/)
- [React Aria Components Docs](https://react-spectrum.adobe.com/react-aria/components.html)
**Stop reinventing the wheel. Import the wheel, and paint it whatever color you want.**
## Related Articles
- · [React Compiler: The End of Manual Memoization and the Era of Auto-Optimization](https://devproportal.com/frontend/react/react-compiler-eliminates-memoization/)
- · [Mastering Concurrent Rendering: A Deep Dive into Transitions and Deferring](https://devproportal.com/frontend/react/mastering-react-concurrent-mode-transitions-deferring/)
- · [React 19 Deep Dive: Mastering the Compiler, Actions, and Advanced Hooks](https://devproportal.com/frontend/react/react-19-deep-dive-compiler-actions-hooks/)
- · [State of React State: Redux Toolkit vs. Zustand vs. Signals](https://devproportal.com/frontend/react/react-state-management-showdown-redux-zustand-signals/)
- · [React 19 Architecture: Mastering the Server vs. Client Component Paradigm](https://devproportal.com/frontend/react/react-19-server-vs-client-components-guide/)
## The Architects Pulse: Engineering Intelligence
As a CTO with 21+ years of experience, I deconstruct the complexities of high-performance backends. Join our technical circle to receive weekly strategic drills on JVM internals, Go concurrency, and cloud-native resilience. No fluff, just pure architectural execution.
Which technical challenge are you currently deconstructing?
Option A: Master patterns in Go, Java, and Node.js.
Option B: Deep-dive into Database internals and Sharding.
Option C: Orchestrating resilience with K8s and Microservices.
Option D: Deconstructing real-world architectural case studies.
Join the Inner Circle
[Built with Kit](https://kit.com/features/forms?utm_campaign=poweredby&utm_content=form&utm_medium=referral&utm_source=dynamic)
[](https://devproportal.com/frontend/react/mastering-headless-ui-radix-vs-react-aria/#the-top "Scroll to top")
* * *
[![DevPro Portal Logo](https://devproportal.com/images/devproportal_banner_light.jpg)![DevPro Portal Logo](https://devproportal.com/images/devproportal_banner_dark.jpg)](https://devproportal.com/)
© 2026 DevPro Portal. All rights reserved.
Powered by [Stonehenge EdTech](https://www.stonehengeedtech.com/).
[About Us](https://devproportal.com/about-us) [Terms of Service](https://devproportal.com/terms-of-service) [Privacy Policy](https://devproportal.com/privacy-policy) [Cookie Policy](https://devproportal.com/cookie-policy) [Support & Contact](https://devproportal.com/contact)

View File

@ -1,224 +0,0 @@
[Back to Blog](https://mohameddewidar.com/blog)
# Headless UI Patterns in React
Headless UI is a pattern that separates **logic** from **presentation**. The component controls state and behavior while leaving all markup and styling to the consumer. This pattern is used in modern design systems and UI libraries because it gives developers full visual freedom without sacrificing predictable logic.
This article explains the pattern clearly, shows how to implement it in React, and provides real world examples that scale in production systems.
## 1\. What Headless UI Means
A headless component does not render its own layout. It exposes:
- state
- actions
- event handlers
- accessibility helpers
The consumer decides how the UI looks.
Example of a headless toggle hook:
```
function useToggle(initial = false) {
const [on, setOn] = useState(initial);
return {
on,
toggle: () => setOn((v) => !v),
setOn
};
}
```
Usage with custom UI:
```
const toggle = useToggle();
<button onClick={toggle.toggle}>
{toggle.on ? "On" : "Off"}
</button>
```
The hook manages behavior. The UI is entirely customizable.
## 2\. Why Headless UI Matters
Headless components solve real problems:
### Problem 1. Components that restrict styling
Teams often fight against rigid components that force markup or CSS structures.
### Problem 2. Hard to integrate with custom design systems
Headless logic works with any HTML structure and any design system.
### Problem 3. Difficult to reuse logic without duplicating UI
The logic lives in one place. The UI can vary by use case.
### Problem 4. Mixed concerns
UI, behavior, and state are combined in one file. Headless UI splits these into clear boundaries.
## 3\. Headless UI With Render Props
Render props expose behavior through a function.
```
function Dropdown({ children }) {
const [open, setOpen] = useState(false);
return children({
open,
toggle: () => setOpen((v) => !v)
});
}
```
Usage:
```
<Dropdown>
{({ open, toggle }) => (
<>
<button onClick={toggle}>Menu</button>
{open && <div className="menu">...</div>}
</>
)}
</Dropdown>
```
This gives complete visual control.
## 4\. Headless UI With Context and Compound Components
This pattern is cleaner for multi part components like modals, dropdowns, and accordions.
### Step 1. Create context driven logic
```
const AccordionContext = createContext(null);
function Accordion({ children }) {
const [open, setOpen] = useState(false);
const value = {
open,
toggle: () => setOpen((v) => !v)
};
return (
<AccordionContext.Provider value={value}>
{children}
</AccordionContext.Provider>
);
}
```
### Step 2. Separate UI components
```
function AccordionTrigger({ children }) {
const { toggle } = useContext(AccordionContext);
return <button onClick={toggle}>{children}</button>;
}
function AccordionContent({ children }) {
const { open } = useContext(AccordionContext);
return open ? <div>{children}</div> : null;
}
```
Usage:
```
<Accordion>
<AccordionTrigger>Show Details</AccordionTrigger>
<AccordionContent>Here are the details...</AccordionContent>
</Accordion>
```
The logic is shared. The UI remains flexible.
## 5\. Building a Headless Select Component
Logic:
```
function useSelect(items) {
const [selected, setSelected] = useState(null);
return {
items,
selected,
select: (item) => setSelected(item)
};
}
```
Usage:
```
const select = useSelect(["A", "B", "C"]);
<ul>
{select.items.map((item) => (
<li key={item} onClick={() => select.select(item)}>
{item} {select.selected === item ? "(selected)" : ""}
</li>
))}
</ul>
```
## 6\. Accessibility in Headless UI
A headless design system should expose accessibility helpers.
```
function useDialog() {
const [open, setOpen] = useState(false);
return {
open,
triggerProps: {
"aria-haspopup": "dialog",
onClick: () => setOpen(true)
},
dialogProps: {
role: "dialog",
"aria-modal": true
},
close: () => setOpen(false)
};
}
```
Usage:
```
const dialog = useDialog();
<button {...dialog.triggerProps}>Open</button>
{dialog.open && (
<div {...dialog.dialogProps}>
Modal content
<button onClick={dialog.close}>Close</button>
</div>
)}
```
## 7\. Anti Patterns to Avoid
- Forcing DOM structure
- Mixing styling inside the logic
- Bloated configuration props
- Hooks that try to manage both logic and rendering
## Final Thoughts
Headless UI patterns help teams build flexible and reusable components that scale with evolving design requirements. By separating logic from presentation, React applications become easier to maintain, more customizable, and more predictable.
[Back to Blog](https://mohameddewidar.com/blog)

View File

@ -1,597 +0,0 @@
The Generative UI Framework
# AI → json-render → UI
Generate dynamic, personalized UIs from prompts without sacrificing reliability. Predefined components and actions for safe, predictable output.
Create a contact form with name, email, and message
Create a login form with email and passwordBuild a feedback form with rating stars
jsonnestedstreamcatalog
```
{"op":"add","path":"/root","value":"card"}
{"op":"add","path":"/elements/name","value":{"type":"Input","props":{"label":"Name","name":"name","statePath":"/form/name","checks":[{"type":"required","message":"Name is required"}]}}}
{"op":"add","path":"/elements/email","value":{"type":"Input","props":{"label":"Email","name":"email","type":"email","statePath":"/form/email","checks":[{"type":"required","message":"Email is required"},{"type":"email","message":"Please enter a valid email"}]}}}
```
```
{
"root": "card",
"state": {
"form": {
"name": "",
"email": "",
"message": ""
}
},
"elements": {
"card": {
"type": "Card",
"props": {
"title": "Contact Us",
"maxWidth": "md"
},
"children": [\
"name",\
"email"\
]
},
"name": {
"type": "Input",
"props": {
"label": "Name",
"name": "name",
"statePath": "/form/name",
"checks": [\
{\
"type": "required",\
"message": "Name is required"\
}\
]
}
},
"email": {
"type": "Input",
"props": {
"label": "Email",
"name": "email",
"type": "email",
"statePath": "/form/email",
"checks": [\
{\
"type": "required",\
"message": "Email is required"\
},\
{\
"type": "email",\
"message": "Please enter a valid email"\
}\
]
}
}
}
}
```
```
{
"state": {
"form": {
"name": "",
"email": "",
"message": ""
}
},
"elements": {
"type": "Card",
"props": {
"title": "Contact Us",
"maxWidth": "md"
},
"children": [\
{\
"type": "Input",\
"props": {\
"label": "Name",\
"name": "name",\
"statePath": "/form/name",\
"checks": [\
{\
"type": "required",\
"message": "Name is required"\
}\
]\
}\
},\
{\
"type": "Input",\
"props": {\
"label": "Email",\
"name": "email",\
"type": "email",\
"statePath": "/form/email",\
"checks": [\
{\
"type": "required",\
"message": "Email is required"\
},\
{\
"type": "email",\
"message": "Please enter a valid email"\
}\
]\
}\
}\
]
}
}
```
components (39)actions (6)
Accordion
Collapsible sections. Items as \[{title, content}\]. Type 'single' (default) or 'multiple'.
items: arraytype: enum?
Alert
Alert banner
title: stringmessage: string?type: enum?
Avatar
User avatar with fallback initials
src: string?name: stringsize: enum?
Badge
Status badge
text: stringvariant: enum?
BarGraph
Vertical bar chart
title: string?data: array
Button
Clickable button. Bind on.press for handler.
label: stringvariant: enum?disabled: boolean?
on.press
ButtonGroup
Segmented button group. Use { $bindState } on selected for selected value.
buttons: arrayselected: string?
on.change
Cardslots: default
Container card for content sections. Use for forms/content boxes, NOT for page headers.
title: string?description: string?maxWidth: enum?centered: boolean?
Carousel
Horizontally scrollable carousel of cards.
items: array
Checkbox
Checkbox input. Use { $bindState } on checked for binding.
label: stringname: stringchecked: boolean?
on.change
Collapsibleslots: default
Collapsible section with trigger. Children render inside.
title: stringdefaultOpen: boolean?
Dialogslots: default
Modal dialog. Set openPath to a boolean state path. Use setState to toggle.
title: stringdescription: string?openPath: string
Drawerslots: default
Bottom sheet drawer. Set openPath to a boolean state path. Use setState to toggle.
title: stringdescription: string?openPath: string
DropdownMenu
Dropdown menu with trigger button and selectable items.
label: stringitems: array
on.select
Gridslots: default
Grid layout (1-6 columns)
columns: number?gap: enum?
Heading
Heading text (h1-h4)
text: stringlevel: enum?
Image
Placeholder image (displays alt text in a styled box)
alt: stringwidth: number?height: number?
Input
Text input field. Use { $bindState } on value for two-way binding. Use checks for validation (e.g. required, email, minLength).
label: stringname: stringtype: enum?placeholder: string?value: string?checks: array?
on.submiton.focuson.blur
LineGraph
Line chart with points
title: string?data: array
Link
Anchor link. Bind on.press for click handler.
label: stringhref: string
on.press
Pagination
Page navigation. Use { $bindState } on page for current page number.
totalPages: numberpage: number?
on.change
Popover
Popover that appears on click of trigger.
trigger: stringcontent: string
Progress
Progress bar (value 0-100)
value: numbermax: number?label: string?
Radio
Radio button group. Use { $bindState } on value for binding.
label: stringname: stringoptions: arrayvalue: string?
on.change
Rating
Star rating display
value: numbermax: number?label: string?
Select
Dropdown select input. Use { $bindState } on value for binding. Use checks for validation.
label: stringname: stringoptions: arrayplaceholder: string?value: string?checks: array?
on.change
Separator
Visual separator line
orientation: enum?
Skeleton
Loading placeholder skeleton
width: string?height: string?rounded: boolean?
Slider
Range slider input. Use { $bindState } on value for binding.
label: string?min: number?max: number?step: number?value: number?
on.change
Spinner
Loading spinner indicator
size: enum?label: string?
Stackslots: default
Flex container for layouts
direction: enum?gap: enum?align: enum?justify: enum?
Switch
Toggle switch. Use { $bindState } on checked for binding.
label: stringname: stringchecked: boolean?
on.change
Table
Data table. columns: header labels. rows: 2D array of cell strings, e.g. \[\["Alice","admin"\],\["Bob","user"\]\].
columns: arrayrows: arraycaption: string?
Tabs
Tab navigation. Use { $bindState } on value for active tab binding.
tabs: arraydefaultValue: string?value: string?
on.change
Text
Paragraph text
text: stringvariant: enum?
Textarea
Multi-line text input. Use { $bindState } on value for binding. Use checks for validation.
label: stringname: stringplaceholder: string?rows: number?value: string?checks: array?
Toggle
Toggle button. Use { $bindState } on pressed for state binding.
label: stringpressed: boolean?variant: enum?
on.change
ToggleGroup
Group of toggle buttons. Type 'single' (default) or 'multiple'. Use { $bindState } on value.
items: arraytype: enum?value: string?
on.change
Tooltip
Hover tooltip. Shows content on hover over text.
content: stringtext: string
live renderstatic code
export
### Contact Us
Name
Email
`npm install @json-render/core @json-render/react`
[Get Started](https://json-render.dev/docs) [GitHub](https://github.com/vercel-labs/json-render)
01
### Define Your Catalog
Set the guardrails. Define which components, actions, and data bindings AI can use.
02
### AI Generates
Describe what you want. AI generates JSON constrained to your catalog. Every interface is unique.
03
### Render Instantly
Stream the response. Your components render progressively as JSON arrives.
## Define your catalog
Components, actions, and validation functions.
```
import { defineSchema, defineCatalog } from '@json-render/core';
import { z } from 'zod';
const schema = defineSchema({ /* ... */ });
export const catalog = defineCatalog(schema, {
components: {
Card: {
props: z.object({
title: z.string(),
description: z.string().nullable(),
}),
hasChildren: true,
},
Metric: {
props: z.object({
label: z.string(),
statePath: z.string(),
format: z.enum(['currency', 'percent']),
}),
},
},
actions: {
export: { params: z.object({ format: z.string() }) },
},
});
```
Show all
## AI generates JSON
Constrained output that your components render natively.
```
{
"root": "dashboard",
"elements": {
"dashboard": {
"type": "Card",
"props": {
"title": "Revenue Dashboard"
},
"children": ["revenue"]
},
"revenue": {
"type": "Metric",
"props": {
"label": "Total Revenue",
"statePath": "/metrics/revenue",
"format": "currency"
}
}
}
}
```
## Export as Code
Export generated UI as standalone React components. No runtime dependencies required.
### Generated UI Tree
AI generates a JSON structure from the user's prompt.
```
{
"root": "card",
"elements": {
"card": {
"type": "Card",
"props": { "title": "Revenue" },
"children": ["metric", "chart"]
},
"metric": {
"type": "Metric",
"props": {
"label": "Total Revenue",
"statePath": "analytics/revenue",
"format": "currency"
}
},
"chart": {
"type": "Chart",
"props": {
"statePath": "analytics/salesByRegion"
}
}
}
}
```
Show all
### Exported React Code
Export as a standalone Next.js project with all components.
```
"use client";
import { Card, Metric, Chart } from "@/components/ui";
const data = {
analytics: {
revenue: 125000,
salesByRegion: [\
{ label: "US", value: 45000 },\
{ label: "EU", value: 35000 },\
],
},
};
export default function Page() {
return (
<Card data={data} title="Revenue">
<Metric
data={data}
label="Total Revenue"
statePath="analytics/revenue"
format="currency"
/>
<Chart data={data} statePath="analytics/salesByRegion" />
</Card>
);
}
```
Show all
The export includes`package.json`, component files, styles, and everything needed to run independently.
## Features
### Generative UI
Generate dynamic, personalized interfaces from prompts with AI
### Guardrails
AI can only use components you define in the catalog
### Streaming
Progressive rendering as JSON streams from the model
### React & React Native
Render on web and mobile from the same catalog and spec format
### Data Binding
Connect props to state with $state, $item, $index, and two-way binding
### Code Export
Export as standalone React code with no runtime dependencies
## Get started
`npm install @json-render/core @json-render/react`
[Documentation](https://json-render.dev/docs)
Ask AI `⌘I`

View File

@ -0,0 +1,73 @@
# Animation
Kobalte components can be animated with CSS keyframes or your JavaScript animation library of choice. However, there are some caveats noted here in regard to exiting animations with JavaScript animation libraries.
## Animating with CSS animation
The simplest way to animate Kobalte components is with CSS.
You can use CSS animation to animate both mount and unmount phases of several Kobalte components. The latter is possible because this components will suspend unmount while your animation plays out.
```
Copycss
.popover__content {
animation: fadeOut 300ms ease-in forwards;
}
.popover__content[data-expanded] {
animation: fadeIn 300ms ease-out;
}
@keyframes fadeIn {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
@keyframes fadeOut {
from {
opacity: 1;
}
to {
opacity: 0;
}
}
```
```
Copycss
.popover__content {
animation: fadeOut 300ms ease-in forwards;
}
.popover__content[data-expanded] {
animation: fadeIn 300ms ease-out;
}
@keyframes fadeIn {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
@keyframes fadeOut {
from {
opacity: 1;
}
to {
opacity: 0;
}
}
```
## Delegating unmounting for JavaScript animation
When many stateful Kobalte components are hidden from view, they are actually removed from the DOM. JavaScript animation libraries need control of the unmounting phase, so we provide the `forceMount` prop on many components to allow consumers to delegate the mounting and unmounting of children based on the animation state determined by those libraries.
Previous[←Styling](https://kobalte.dev/docs/core/overview/styling)Next[Polymorphism→](https://kobalte.dev/docs/core/overview/polymorphism)

View File

@ -0,0 +1,129 @@
# v0.13.x
## v0.13.11 (July 27, 2025)
**Bug fixes**
- Update createInteractOutside to work with other Element types ( [#612](https://github.com/kobaltedev/kobalte/pull/612))
- Meter, Progress: remove rounding from fill width ( [#602](https://github.com/kobaltedev/kobalte/pull/602))
- Image: handle crossOrigin and referrerPolicy ( [#598](https://github.com/kobaltedev/kobalte/pull/598))
- ToggleGroup: reactive disabled state ( [#595](https://github.com/kobaltedev/kobalte/pull/595))
- ColorArea: double onChange trigger ( [#593](https://github.com/kobaltedev/kobalte/pull/593))
- ColorChannelField: fix handling of alpha values ( [#590](https://github.com/kobaltedev/kobalte/pull/590))
- Colors: color parsing and rgb(a)/hsl(a) format ( [#587](https://github.com/kobaltedev/kobalte/pull/587))
## v0.13.10 (May 30, 2025)
**New features**
- [New `SegmentedControl` component](https://kobalte.dev/docs/core/components/segmented-control) ( [#531](https://github.com/kobaltedev/kobalte/pull/531))
## v0.13.9 (February 21, 2025)
**New features**
- [New `ColorWheel` component](https://kobalte.dev/docs/core/components/color-wheel) ( [#510](https://github.com/kobaltedev/kobalte/pull/510))
- [New `ColorField` component](https://kobalte.dev/docs/core/components/color-field) ( [#510](https://github.com/kobaltedev/kobalte/pull/510))
- [New `ColorChannelField` component](https://kobalte.dev/docs/core/components/color-channel-field) ( [#508](https://github.com/kobaltedev/kobalte/pull/508))
- [New `ColorArea` component](https://kobalte.dev/docs/core/components/color-area) ( [#508](https://github.com/kobaltedev/kobalte/pull/508))
- [New `ColorSlider` component](https://kobalte.dev/docs/core/components/color-slider) ( [#508](https://github.com/kobaltedev/kobalte/pull/508))
- [New `ColorSwatch` component](https://kobalte.dev/docs/core/components/color-swatch) ( [#508](https://github.com/kobaltedev/kobalte/pull/508))
- [New `Search` component](https://kobalte.dev/docs/core/components/search) ( [#506](https://github.com/kobaltedev/kobalte/pull/506))
## v0.13.8 (February 7, 2025)
**New features**
- [New `FileField` component](https://kobalte.dev/docs/core/components/file-field) ( [#512](https://github.com/kobaltedev/kobalte/pull/512))
- [New `Badge` component](https://kobalte.dev/docs/core/components/badge) ( [#503](https://github.com/kobaltedev/kobalte/pull/503))
- [New `Meter` component](https://kobalte.dev/docs/core/components/meter) ( [#500](https://github.com/kobaltedev/kobalte/pull/500))
## v0.13.7 (October 7, 2024)
**Bug fixes**
- ToggleGroup: root value type definition ( [#479](https://github.com/kobaltedev/kobalte/pull/479))
- Collapsible: close animation on default open ( [#483](https://github.com/kobaltedev/kobalte/pull/483))
- Toast: filter dismissed toasts in region ( [#489](https://github.com/kobaltedev/kobalte/pull/489))
## v0.13.6 (August 27, 2024)
**Bug fixes**
- Update missing export from `@kobalte/utils` ( [#477](https://github.com/kobaltedev/kobalte/pull/477))
## v0.13.5 (August 27, 2024)
**New features**
- Tooltip: add skipDelayDuration prop ( [#467](https://github.com/kobaltedev/kobalte/pull/467))
**Bug fixes**
- NumberField: only format when enabled ( [#456](https://github.com/kobaltedev/kobalte/pull/456))
- NumberField: don't trigger `onRawValueChange` on mount when NaN ( [#456](https://github.com/kobaltedev/kobalte/pull/456))
- Select: correct type definition & empty value for multiselect ( [#456](https://github.com/kobaltedev/kobalte/pull/456))
- TextField: clear input when controlled value set to undefined ( [#456](https://github.com/kobaltedev/kobalte/pull/456))
- Combobox: correct type definition & empty value for multiselect ( [#456](https://github.com/kobaltedev/kobalte/pull/456))
- Skeleton: correct data-animate & data-visible attribute value ( [#456](https://github.com/kobaltedev/kobalte/pull/456))
- Combobox: close list on outside click ( [#456](https://github.com/kobaltedev/kobalte/pull/456))
- NavigationMenu: incorrect animation after closed ( [#456](https://github.com/kobaltedev/kobalte/pull/456))
- Tabs: recalculate indicator styles on resize ( [#458](https://github.com/kobaltedev/kobalte/pull/458))
- Pagination: correctly render fixedItems with low page count ( [#464](https://github.com/kobaltedev/kobalte/pull/464))
- Combobox: prevent opening on input with triggerMode="manual" ( [#465](https://github.com/kobaltedev/kobalte/pull/465))
- NumberField: precision handling with floating point offsets and value snapping ( [#468](https://github.com/kobaltedev/kobalte/pull/468))
## v0.13.4 (June 22, 2024)
**New features**
- Add source exports ( [#408](https://github.com/kobaltedev/kobalte/pull/408))
- Support string style prop ( [#432](https://github.com/kobaltedev/kobalte/pull/432))
**Bug fixes**
- Combobox: convert textValue with optionTextValue ( [#436](https://github.com/kobaltedev/kobalte/pull/436))
- Polymorphic: override the `component` prop of `<Dynamic />` ( [#437](https://github.com/kobaltedev/kobalte/pull/437))
- Slider: inverted slider thumb in the wrong position ( [#441](https://github.com/kobaltedev/kobalte/pull/441))
- Wait for presence to finish before enabling scroll ( [#447](https://github.com/kobaltedev/kobalte/pull/447))
## v0.13.3 (June 3, 2024)
**New features**
- [New `NavigationMenu` component](https://kobalte.dev/docs/core/components/navigation-menu) ( [#409](https://github.com/kobaltedev/kobalte/pull/409))
## v0.13.2 (May 29, 2024)
**Bug fixes**
- Refactored polymorphic element type in CommonProps ( [#420](https://github.com/kobaltedev/kobalte/pull/420))
## v0.13.1 (May 3, 2024)
**New features**
- NumberField: improve input handling ( [#379](https://github.com/kobaltedev/kobalte/pull/379)) ( [#395](https://github.com/kobaltedev/kobalte/pull/395))
**Bug fixes**
- Slider: call `onChangeEnd` on blur after changing value ( [#402](https://github.com/kobaltedev/kobalte/pull/402))
- Select: `options` non reactive inside suspense ( [#401](https://github.com/kobaltedev/kobalte/pull/401))
- Combobox: close on select with `focus` trigger mode ( [#400](https://github.com/kobaltedev/kobalte/pull/400))
- Menu: open link menu items ( [#397](https://github.com/kobaltedev/kobalte/pull/397))
## v0.13.0 (May 1, 2024)
**Breaking changes**
- [#381](https://github.com/kobaltedev/kobalte/pull/381)
- Removed `asChild` and `<As/>`: [Polymorphism documentation](https://kobalte.dev/docs/core/overview/polymorphism)
- Refactored `as` prop: [Polymorphism documentation](https://kobalte.dev/docs/core/overview/polymorphism)
- [New component types](https://kobalte.dev/docs/core/overview/polymorphism#types)
**New features**
- Allow importing individual components ( [#391](https://github.com/kobaltedev/kobalte/pull/391))
- [New `ToggleGroup` component](https://kobalte.dev/docs/core/components/toggle-group) ( [#378](https://github.com/kobaltedev/kobalte/pull/378))
Next[v0.12.x→](https://kobalte.dev/docs/changelog/0-12-x)

View File

@ -0,0 +1,349 @@
# Color Area
Allows users to adjust two channels of an RGB, HSL, or HSB color value against a two-dimensional gradient background.
## Import
```
Copyts
import { ColorArea } from "@kobalte/core/color-area";
// or
import { Root, Background, ... } from "@kobalte/core/color-area";
```
```
Copyts
import { ColorArea } from "@kobalte/core/color-area";
// or
import { Root, Background, ... } from "@kobalte/core/color-area";
```
## Features
- Can be controlled or uncontrolled.
- Support for adjusting two-channel values of an RGB, HSL or HSB color value.
- Pressing on the color area background moves the thumb to that position.
- Supports using the arrow keys, for changing value by step, as well as shift + arrow key, page up/down, home, and end keys, for changing the value by page step.
- Support for disabling the color area.
- Prevents text selection while dragging.
- Exposed to assistive technology as a 2D slider element via ARIA.
- Uses two hidden native input elements within a group to support touch screen readers.
- Automatic ARIA labeling using the localized channel names by default.
- Support for mirroring in RTL locales.
## Anatomy
The color area consists of:
- **ColorArea:** The root container for the color area.
- **ColorArea.Background:** The component that visually represents the range of colors from which a user can select.
- **ColorArea.Thumb:** The thumb that is used to visually indicate a value in the color area background.
- **ColorArea.HiddenInputX:** The horizontal native html input that is visually hidden in the color area thumb.
- **ColorArea.HiddenInputY:** The vertical native html input that is visually hidden in the color area thumb.
- **ColorArea.Label:** The label that gives the user information on the color area.
```
Copytsx
<ColorArea>
<ColorArea.Label />
<ColorArea.Background>
<ColorArea.Thumb>
<ColorArea.HiddenInputX />
<ColorArea.HiddenInputY />
</ColorArea.Thumb>
</ColorArea.Track>
</ColorArea>
```
```
Copytsx
<ColorArea>
<ColorArea.Label />
<ColorArea.Background>
<ColorArea.Thumb>
<ColorArea.HiddenInputX />
<ColorArea.HiddenInputY />
</ColorArea.Thumb>
</ColorArea.Track>
</ColorArea>
```
## Example
Label
index.tsxstyle.css
```
Copytsx
import { ColorArea } from "@kobalte/core/color-area";
import { parseColor } from "@kobalte/utils";
import "./style.css";
function App() {
return (
<ColorArea class="ColorAreaRoot">
<ColorArea.Label class="ColorAreaLabel">Label</ColorArea.Label>
<ColorArea.Background class="ColorAreaBackground">
<ColorArea.Thumb class="ColorAreaThumb">
<ColorArea.HiddenInputX />
<ColorArea.HiddenInputY />
</ColorArea.Thumb>
</ColorArea.Background>
</ColorArea>
);
}
```
```
Copytsx
import { ColorArea } from "@kobalte/core/color-area";
import { parseColor } from "@kobalte/utils";
import "./style.css";
function App() {
return (
<ColorArea class="ColorAreaRoot">
<ColorArea.Label class="ColorAreaLabel">Label</ColorArea.Label>
<ColorArea.Background class="ColorAreaBackground">
<ColorArea.Thumb class="ColorAreaThumb">
<ColorArea.HiddenInputX />
<ColorArea.HiddenInputY />
</ColorArea.Thumb>
</ColorArea.Background>
</ColorArea>
);
}
```
## Usage
The value provided to `defaultValue` or `value` should be `Color` object. You can obtain a Color object by using the `parseColor` function to parse a color from a string.
The `xChannel` and `yChannel` must be one of the channels included in the color value, for example, for RGB colors, the "red", "green", and "blue" channels are available.
If no `xChannel` or `yChannel` is provided, for the RGB color space, the red color channel maps to the horizontal axis or xChannel, and the green color channel maps to the vertical axis or yChannel. Similarly, for the HSL and HSB color spaces, the hue color channel maps to the horizontal axis or xChannel, and the saturation color channel maps to the vertical axis or yChannel.
### Default value
Label
```
Copytsx
<ColorArea defaultValue={parseColor("rgb(2, 132, 197)")}>
<ColorArea.Label>Label</ColorArea.Label>
<ColorArea.Background>
<ColorArea.Thumb>
<ColorArea.HiddenInputX />
<ColorArea.HiddenInputY />
</ColorArea.Thumb>
</ColorArea.Background>
</ColorArea>
```
```
Copytsx
<ColorArea defaultValue={parseColor("rgb(2, 132, 197)")}>
<ColorArea.Label>Label</ColorArea.Label>
<ColorArea.Background>
<ColorArea.Thumb>
<ColorArea.HiddenInputX />
<ColorArea.HiddenInputY />
</ColorArea.Thumb>
</ColorArea.Background>
</ColorArea>
```
### Controlled value
Label
Current color value: hsl(0 100% 50%)
```
Copytsx
import { createSignal } from "solid-js";
const [value, setValue] = createSignal(parseColor("hsl(0, 100%, 50%)"));
<>
<ColorArea value={value()} onChange={setValue}>
<ColorArea.Label>Label</ColorArea.Label>
<ColorArea.Background>
<ColorArea.Thumb>
<ColorArea.HiddenInputX />
<ColorArea.HiddenInputY />
</ColorArea.Thumb>
</ColorArea.Background>
</ColorArea>
<p>Current color value: {value().toString("hsl")}</p>
</>;
```
```
Copytsx
import { createSignal } from "solid-js";
const [value, setValue] = createSignal(parseColor("hsl(0, 100%, 50%)"));
<>
<ColorArea value={value()} onChange={setValue}>
<ColorArea.Label>Label</ColorArea.Label>
<ColorArea.Background>
<ColorArea.Thumb>
<ColorArea.HiddenInputX />
<ColorArea.HiddenInputY />
</ColorArea.Thumb>
</ColorArea.Background>
</ColorArea>
<p>Current color value: {value().toString("hsl")}</p>
</>;
```
### xChannel and yChannel
The color channel for each axis of a color area can be specified using the `xChannel` and `yChannel` props. An array of channel names for a color can be returned using the color.getColorChannels method.
Label
```
Copytsx
const [value, setValue] = createSignal(parseColor("rgb(100, 149, 237)"));
const [rChannel, gChannel, bChannel] = value().getColorChannels();
<ColorArea value={value()} onChange={setValue} xChannel={gChannel} yChannel={bChannel}>
<ColorArea.Label>Label</ColorArea.Label>
<ColorArea.Background>
<ColorArea.Thumb>
<ColorArea.HiddenInputX />
<ColorArea.HiddenInputY />
</ColorArea.Thumb>
</ColorArea.Background>
</ColorArea>;
```
```
Copytsx
const [value, setValue] = createSignal(parseColor("rgb(100, 149, 237)"));
const [rChannel, gChannel, bChannel] = value().getColorChannels();
<ColorArea value={value()} onChange={setValue} xChannel={gChannel} yChannel={bChannel}>
<ColorArea.Label>Label</ColorArea.Label>
<ColorArea.Background>
<ColorArea.Thumb>
<ColorArea.HiddenInputX />
<ColorArea.HiddenInputY />
</ColorArea.Thumb>
</ColorArea.Background>
</ColorArea>;
```
### HTML forms
ColorArea supports the `xName` and `yName` props for integration with HTML forms.
Label
ResetSubmit
```
Copytsx
<ColorArea defaultValue={parseColor("rgb(100, 149, 237)")} xName="red" yName="green">
<ColorArea.Label>Label</ColorArea.Label>
<ColorArea.Background>
<ColorArea.Thumb>
<ColorArea.HiddenInputX />
<ColorArea.HiddenInputY />
</ColorArea.Thumb>
</ColorArea.Background>
</ColorArea>
```
```
Copytsx
<ColorArea defaultValue={parseColor("rgb(100, 149, 237)")} xName="red" yName="green">
<ColorArea.Label>Label</ColorArea.Label>
<ColorArea.Background>
<ColorArea.Thumb>
<ColorArea.HiddenInputX />
<ColorArea.HiddenInputY />
</ColorArea.Thumb>
</ColorArea.Background>
</ColorArea>
```
## API Reference
### ColorArea
`ColorArea` is equivalent to the `Root` import from `@kobalte/core/color-area`.
| Prop | Description |
| --- | --- |
| value | `Color`<br> The controlled value of the color area.. |
| defaultValue | `Color`<br> The value of the color area when initially rendered. |
| colorSpace | `ColorSpace`<br> The color space that the color area operates in. The `xChannel` and `yChannel` must be in this color space. |
| onChange | `(value: Color) => void`<br> Event handler called when the value changes. |
| onChangeEnd | `(value: Color) => void`<br> Event handler called when the value changes at the end of an interaction. |
| xChannel | `ColorChannel`<br> Color channel for the horizontal axis. |
| yChannel | `ColorChannel`<br> Color channel for the vertical axis. |
| xName | `string`<br> The name of the x channel input element, used when submitting an HTML form. See [MDN](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#htmlattrdefname). |
| yName | `string`<br> The name of the y channel input element, used when submitting an HTML form. See [MDN](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#htmlattrdefname). |
| name | `string`<br> The name of the color area, used when submitting an HTML form. |
| validationState | `'valid' | 'invalid'`<br> Whether the color area should display its "valid" or "invalid" visual styling. |
| required | `boolean`<br> Whether the user must check a radio group item before the owning form can be submitted. |
| disabled | `boolean`<br> Whether the radio group is disabled. |
| readOnly | `boolean`<br> Whether the radio group items can be selected but not changed by the user. |
| translations | `ColorAreaIntlTranslations`<br> Localization strings. |
| Data attribute | Description |
| --- | --- |
| data-valid | Present when the slider is valid according to the validation rules. |
| data-invalid | Present when the slider is invalid according to the validation rules. |
| data-required | Present when the user must slider an item before the owning form can be submitted. |
| data-disabled | Present when the slider is disabled. |
| data-readonly | Present when the slider is read only. |
`ColorArea.Background`, `ColorArea.Thumb`, `ColorArea.HiddenInputX`, `ColorArea.HiddenInputY` and `ColorArea.Label` share the same data-attributes.
### ColorArea.Thumb
The current color is available on the thumb using the custom css property `--kb-color-current`.
### ColorArea.HiddenInputX
| Data attribute | Description |
| --- | --- |
| data-orientation='horizontal' | Always presesnt |
### ColorArea.HiddenInputY
| Data attribute | Description |
| --- | --- |
| data-orientation='vertical' | Always presesnt |
## Rendered elements
| Component | Default rendered element |
| --- | --- |
| `ColorArea` | `div` |
| `ColorArea.Background` | `div` |
| `ColorArea.Thumb` | `span` |
| `ColorArea.HiddenInputX` | `input` |
| `ColorArea.HiddenInputY` | `input` |
| `ColorArea.Label` | `label` |
| `ColorArea.Description` | `div` |
| `ColorArea.ErrorMessage` | `div` |
## Accessibility
### Keyboard Interactions
| Key | Description |
| --- | --- |
| `PageUp` | Increments the value of the thumb in the vertical axis by a larger step. |
| `PageDown` | Decrements the value of the thumb in the vertical axis by a larger step. |
| `ArrowDown` | Decrements the value of the thumb in the vertical axis by the step amount. |
| `ArrowUp` | Increments the value of the thumb in the vertical axis by the step amount. |
| `ArrowRight` | Increments the value of the thumb in the horizontal axis by the step amount. |
| `ArrowLeft` | Decrements the value of the thumb in the vertical axis by the step amount. |
| `Home` | Decrements the value of the thumb in the horizontal axis by a larger step. |
| `End` | Increments the value of the thumb in the horizontal axis by a larger step. |
Previous[←Collapsible](https://kobalte.dev/docs/core/components/collapsible)Next[Color Channel Field→](https://kobalte.dev/docs/core/components/color-channel-field)

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,378 @@
# File Field
A file input to upload multiple files, with ability to drag and drop files.
## Import
```
Copyts
import { FileField } from "@kobalte/core/file-field";
// or
import { Root } from "@kobalte/core/file-field";
```
```
Copyts
import { FileField } from "@kobalte/core/file-field";
// or
import { Root } from "@kobalte/core/file-field";
```
## Features
- Supports uploading multiple files at once.
- Supports drag and drop functionality for uploading files.
- Supports maximum file limits to restrict the number of files that can be uploaded.
- Supports file size validation to ensure that files are within the specified size limits.
- Supports custom validation rules for uploaded files.
- Supports restricting the types of files that can be uploaded (e.g., images, documents).
- Displays a list of files that have been selected for upload.
- Allows users to remove files from the upload list before uploading.
- Provides a preview of image files before they are uploaded.
- Displays the size of each file in the upload list.
- Supports customizing the components with custom styles and classes.
## Anatomy
The File Field consists of:
- **FileField**: The root container for the File Field component.
- **FileField.Dropzone**: The dropzone area where files can be dragged and dropped.
- **FileField.Trigger**: The trigger element that opens the file selection dialog.
- **FileField.Label**: The label for the File Field component to toggle the file selection dialog.
- **FileField.HiddenInput**: The hidden input element that triggers the file selection dialog.
- **FileField.ItemList**: The container for the list of files that have been selected for upload.
- **FileField.Item**: The individual file item in the list of files that have been selected for upload.
- **FileField.ItemPreview**: The preview for matching file types that have been selected for upload.
- **FileField.ItemPreviewImage**: The preview image for image files that have been selected for upload.
- **FileField.ItemName**: The name of the file that has been selected for upload.
- **FileField.ItemSize**: The size of the file that has been selected for upload.
- **FileField.ItemDeleteTrigger**: The trigger element to remove a file from the list of files that have been selected for upload.
- **FileField.Description**: The description that gives the user more information on the file field.
- **FileField.ErrorMessage**: The error message that gives the user information about how to fix a validation error on the file field.
```
Copytsx
<FileField>
<FileField.Label />
<FileField.Dropzone>
<FileField.Trigger />
</FileField.Dropzone>
<FileField.HiddenInput />
<FileField.ItemList>
<FileField.Item>
<FileField.ItemPreview />
<FileField.ItemPreviewImage />
<FileField.ItemSize />
<FileField.ItemName />
<FileField.ItemDeleteTrigger />
</FileField.Item>
</FileField.ItemList>
<FileField.Description />
<FileField.ErrorMessage />
</FileField>
```
```
Copytsx
<FileField>
<FileField.Label />
<FileField.Dropzone>
<FileField.Trigger />
</FileField.Dropzone>
<FileField.HiddenInput />
<FileField.ItemList>
<FileField.Item>
<FileField.ItemPreview />
<FileField.ItemPreviewImage />
<FileField.ItemSize />
<FileField.ItemName />
<FileField.ItemDeleteTrigger />
</FileField.Item>
</FileField.ItemList>
<FileField.Description />
<FileField.ErrorMessage />
</FileField>
```
## Example
File Upload
Drop your files here...Choose files!
index.tsxstyle.css
```
Copytsx
import { FileField } from "@kobalte/core/file-field";
import "./style.css";
function App() {
return (
<FileField
class={style.FileField}
multiple
maxFiles={5}
onFileAccept={(data) => console.log("data", data)}
onFileReject={(data) => console.log("data", data)}
onFileChange={(data) => console.log("data", data)}
>
<FileField.Label class={style.FileField__label}>
File Field
</FileField.Label>
<FileField.Dropzone class={style.FileField__dropzone}>
Drop your files here...
<FileField.Trigger class={style.FileField__trigger}>
Choose files!
</FileField.Trigger>
</FileField.Dropzone>
<FileField.HiddenInput />
<FileField.ItemList class={style.FileField__itemList}>
{(file) => (
<FileField.Item class={style.FileField__item}>
<FileField.ItemPreviewImage
class={style.FileField__itemPreviewImage}
/>
<FileField.ItemName class={style.FileField__itemName} />
<FileField.ItemSize class={style.FileField__itemSize} />
<FileField.ItemDeleteTrigger
class={style.FileField__itemDeleteTrigger}
>
Delete
</FileField.ItemDeleteTrigger>
</FileField.Item>
)}
</FileField.ItemList>
</FileField>
);
}
```
```
Copytsx
import { FileField } from "@kobalte/core/file-field";
import "./style.css";
function App() {
return (
<FileField
class={style.FileField}
multiple
maxFiles={5}
onFileAccept={(data) => console.log("data", data)}
onFileReject={(data) => console.log("data", data)}
onFileChange={(data) => console.log("data", data)}
>
<FileField.Label class={style.FileField__label}>
File Field
</FileField.Label>
<FileField.Dropzone class={style.FileField__dropzone}>
Drop your files here...
<FileField.Trigger class={style.FileField__trigger}>
Choose files!
</FileField.Trigger>
</FileField.Dropzone>
<FileField.HiddenInput />
<FileField.ItemList class={style.FileField__itemList}>
{(file) => (
<FileField.Item class={style.FileField__item}>
<FileField.ItemPreviewImage
class={style.FileField__itemPreviewImage}
/>
<FileField.ItemName class={style.FileField__itemName} />
<FileField.ItemSize class={style.FileField__itemSize} />
<FileField.ItemDeleteTrigger
class={style.FileField__itemDeleteTrigger}
>
Delete
</FileField.ItemDeleteTrigger>
</FileField.Item>
)}
</FileField.ItemList>
</FileField>
);
}
```
## Usage
### HTML forms
By passing name prop to FileField.HiddenInput, you can use FileField in HTML forms.
File Upload
Drop your files here...Choose files!
Submit Files
```
Copytsx
function HTMLFormExample() {
let formRef: HTMLFormElement | undefined;
const onSubmit = (e: SubmitEvent) => {
// handle form submission.
const formData = new FormData(formRef);
const uploadedFiles = formData.getAll("uploaded-files");
};
return (
<form ref={formRef} onSubmit={onSubmit}>
<FileField
multiple
maxFiles={5}
onFileAccept={data => console.log("data", data)}
onFileReject={data => console.log("data", data)}
onFileChange={data => console.log("data", data)}
>
<FileField.Label>File Field</FileField.Label>
<FileField.Dropzone>
Drop your files here...
<FileField.Trigger>Choose files!</FileField.Trigger>
</FileField.Dropzone>
<FileField.HiddenInput name="uploaded-files" />
<FileField.ItemList>
{file => (
<FileField.Item>
<FileField.ItemPreviewImage class={"FileField__itemPreviewImage"} />
<FileField.ItemName />
<FileField.ItemSize />
<FileField.ItemDeleteTrigger>Delete</FileField.ItemDeleteTrigger>
</FileField.Item>
)}
</FileField.ItemList>
</FileField>
<div>
<button type="reset">Reset</button>
<button type="submit">Submit</button>
</div>
</form>
);
}
```
```
Copytsx
function HTMLFormExample() {
let formRef: HTMLFormElement | undefined;
const onSubmit = (e: SubmitEvent) => {
// handle form submission.
const formData = new FormData(formRef);
const uploadedFiles = formData.getAll("uploaded-files");
};
return (
<form ref={formRef} onSubmit={onSubmit}>
<FileField
multiple
maxFiles={5}
onFileAccept={data => console.log("data", data)}
onFileReject={data => console.log("data", data)}
onFileChange={data => console.log("data", data)}
>
<FileField.Label>File Field</FileField.Label>
<FileField.Dropzone>
Drop your files here...
<FileField.Trigger>Choose files!</FileField.Trigger>
</FileField.Dropzone>
<FileField.HiddenInput name="uploaded-files" />
<FileField.ItemList>
{file => (
<FileField.Item>
<FileField.ItemPreviewImage class={"FileField__itemPreviewImage"} />
<FileField.ItemName />
<FileField.ItemSize />
<FileField.ItemDeleteTrigger>Delete</FileField.ItemDeleteTrigger>
</FileField.Item>
)}
</FileField.ItemList>
</FileField>
<div>
<button type="reset">Reset</button>
<button type="submit">Submit</button>
</div>
</form>
);
}
```
## API Reference
### FileField
`FileField` is equivalent to the `Root` import from `@kobalte/core/file-field`.
| Prop | Description |
| --- | --- |
| `multiple` | `boolean`<br> Allows multiple files to be uploaded |
| `maxFiles` | `number`<br> The maximum number of files that can be uploaded |
| `disabled` | `boolean`<br> Disables the file upload component |
| `accept` | `string | string[] | undefined`<br> The types of files that can be uploaded. |
| `allowDragAndDrop` | `boolean`<br> Allows files to be dragged and dropped into the component. |
| `maxFileSize` | `number`<br> The maximum size of the file that can be uploaded in bytes. |
| `minFileSize` | `number`<br> The minimum size of the file that can be uploaded in bytes. |
| `onFileAccept` | `(files: File[]) => void`<br> Callback function that is called when file(s) is(are) accepted. |
| `onFileReject` | `(files: FileRejection[]) => void`<br> Callback function that is called when a file(s) is(are) rejected. |
| `onFileChange` | `(details: Details) => void`<br> Callback function that is called when the list of files changes. |
| `validateFile` | `(file: File) => FileError[] | null`<br> Custom validation function for files. |
| Data attribute | Description |
| --- | --- |
| data-valid | Present when the slider is valid according to the validation rules. |
| data-invalid | Present when the slider is invalid according to the validation rules. |
| data-required | Present when the user must slider an item before the owning form can be submitted. |
| data-disabled | Present when the slider is disabled. |
| data-readonly | Present when the slider is read only. |
`Slider.Trigger` shares the same data-attributes.
### FileField.Item
| Prop | Description |
| --- | --- |
| `file` | `File`<br> Selected file object |
### FileField.ItemSize
| Prop | Description |
| --- | --- |
| `precision` | `number`<br> The number of decimal places to round the file size to. |
### FileField.ItemPreview
| Prop | Description |
| --- | --- |
| `type` | `string`<br> The type of file to preview, only applicable for Images. |
## Rendered elements
| Component | Default rendered element |
| --- | --- |
| `FileField` | `div` |
| `FileField.Dropzone` | `div` |
| `FileField.Trigger` | `button` |
| `FileField.Label` | `label` |
| `FileField.HiddenInput` | `input` |
| `FileField.ItemList` | `ul` |
| `FileField.Item` | `li` |
| `FileField.ItemPreview` | `div` |
| `FileField.ItemPreviewImage` | `img` |
| `FileField.ItemName` | `span` |
| `FileField.ItemSize` | `span` |
| `FileField.ItemDeleteTrigger` | `button` |
| `FileField.Description` | `div` |
| `FileField.ErrorMessage` | `div` |
## Accessibility
### Keyboard Interactions
| Key | Description |
| --- | --- |
| `Space` | When focus is on dropzone, opens the file dialog.<br> When focus is on trigger, opens the file dialog. |
| `Enter` | When focus is on dropzone, opens the file dialog.<br> When focus is on trigger, opens the file dialog. |
Previous[←Dropdown Menu](https://kobalte.dev/docs/core/components/dropdown-menu)Next[Hover Card→](https://kobalte.dev/docs/core/components/hover-card)

View File

@ -0,0 +1,88 @@
# Getting started
## Installation
Install Kobalte by running either of the following:
npmyarnpnpm
```
Copybash
npm install @kobalte/core
```
```
Copybash
npm install @kobalte/core
```
## Using the components
The example below demonstrate how to create a Popover component with Kobalte.
Learn more
index.tsxstyle.css
```
Copytsx
import { Popover } from "@kobalte/core/popover";
import { CrossIcon } from "some-icon-library";
import "./style.css";
function App() {
<Popover>
<Popover.Trigger class="popover__trigger">Learn more</Popover.Trigger>
<Popover.Portal>
<Popover.Content class="popover__content">
<Popover.Arrow />
<div class="popover__header">
<Popover.Title class="popover__title">About Kobalte</Popover.Title>
<Popover.CloseButton class="popover__close-button">
<CrossIcon />
</Popover.CloseButton>
</div>
<Popover.Description class="popover__description">
A UI toolkit for building accessible web apps and design systems with SolidJS.
</Popover.Description>
</Popover.Content>
</Popover.Portal>
</Popover>
}
```
```
Copytsx
import { Popover } from "@kobalte/core/popover";
import { CrossIcon } from "some-icon-library";
import "./style.css";
function App() {
<Popover>
<Popover.Trigger class="popover__trigger">Learn more</Popover.Trigger>
<Popover.Portal>
<Popover.Content class="popover__content">
<Popover.Arrow />
<div class="popover__header">
<Popover.Title class="popover__title">About Kobalte</Popover.Title>
<Popover.CloseButton class="popover__close-button">
<CrossIcon />
</Popover.CloseButton>
</div>
<Popover.Description class="popover__description">
A UI toolkit for building accessible web apps and design systems with SolidJS.
</Popover.Description>
</Popover.Content>
</Popover.Portal>
</Popover>
}
```
In a few lines of code, we've implemented a fully accessible Popover component that :
- Adheres to [WAI-ARIA Dialog](https://www.w3.org/WAI/ARIA/apg/patterns/dialogmodal/) design pattern.
- Can be controlled or uncontrolled.
- Optionally render a pointing arrow.
- Has focus fully managed and customizable.
Previous[←Introduction](https://kobalte.dev/docs/core/overview/introduction)Next[Styling→](https://kobalte.dev/docs/core/overview/styling)

View File

@ -0,0 +1,140 @@
[Skip to content](https://github.com/kobaltedev/kobalte/tree/main/packages/core/src#start-of-content)
You signed in with another tab or window. [Reload](https://github.com/kobaltedev/kobalte/tree/main/packages/core/src) to refresh your session.You signed out in another tab or window. [Reload](https://github.com/kobaltedev/kobalte/tree/main/packages/core/src) to refresh your session.You switched accounts on another tab or window. [Reload](https://github.com/kobaltedev/kobalte/tree/main/packages/core/src) to refresh your session.Dismiss alert
{{ message }}
[kobaltedev](https://github.com/kobaltedev)/ **[kobalte](https://github.com/kobaltedev/kobalte)** Public
- [Sponsor](https://github.com/sponsors/jer3m01)
- [Notifications](https://github.com/login?return_to=%2Fkobaltedev%2Fkobalte) You must be signed in to change notification settings
- [Fork\\
105](https://github.com/login?return_to=%2Fkobaltedev%2Fkobalte)
- [Star\\
1.8k](https://github.com/login?return_to=%2Fkobaltedev%2Fkobalte)
## Collapse file tree
## Files
main
Search this repository(forward slash)` forward slash/`
/
# src
/
Copy path
## Directory actions
## More options
More options
## Directory actions
## More options
More options
## Latest commit
[![teidesu](https://avatars.githubusercontent.com/u/86301490?v=4&size=40)](https://github.com/teidesu)[teidesu](https://github.com/kobaltedev/kobalte/commits?author=teidesu)
[feat(tooltip): expose onCurrentPlacementChange (](https://github.com/kobaltedev/kobalte/commit/8a2d219b442fb5febb7221ef3e685545076b544e) [#637](https://github.com/kobaltedev/kobalte/pull/637) [)](https://github.com/kobaltedev/kobalte/commit/8a2d219b442fb5febb7221ef3e685545076b544e)
failure
3 months agoDec 16, 2025
[8a2d219](https://github.com/kobaltedev/kobalte/commit/8a2d219b442fb5febb7221ef3e685545076b544e) · 3 months agoDec 16, 2025
## History
[History](https://github.com/kobaltedev/kobalte/commits/main/packages/core/src)
Open commit details
[View commit history for this file.](https://github.com/kobaltedev/kobalte/commits/main/packages/core/src) History
/
# src
/
Top
## Folders and files
| Name | Name | Last commit message | Last commit date |
| --- | --- | --- | --- |
| ### parent directory<br> [..](https://github.com/kobaltedev/kobalte/tree/main/packages/core) |
| [accordion](https://github.com/kobaltedev/kobalte/tree/main/packages/core/src/accordion "accordion") | [accordion](https://github.com/kobaltedev/kobalte/tree/main/packages/core/src/accordion "accordion") | [chore: prepare release](https://github.com/kobaltedev/kobalte/commit/91a41e91f45bc5c57bd942ac641137e7745d22a5 "chore: prepare release `0.13.11` (#614)")`0.13.11` [(](https://github.com/kobaltedev/kobalte/commit/91a41e91f45bc5c57bd942ac641137e7745d22a5 "chore: prepare release `0.13.11` (#614)") [#614](https://github.com/kobaltedev/kobalte/pull/614) [)](https://github.com/kobaltedev/kobalte/commit/91a41e91f45bc5c57bd942ac641137e7745d22a5 "chore: prepare release `0.13.11` (#614)") | 8 months agoJul 26, 2025 |
| [alert-dialog](https://github.com/kobaltedev/kobalte/tree/main/packages/core/src/alert-dialog "alert-dialog") | [alert-dialog](https://github.com/kobaltedev/kobalte/tree/main/packages/core/src/alert-dialog "alert-dialog") | [feat: support string style prop (](https://github.com/kobaltedev/kobalte/commit/4dc7f8df7167f0da8f6abd4c402a771d831cecb3 "feat: support string style prop (#432) * chore: update formatter") [#432](https://github.com/kobaltedev/kobalte/pull/432) [)](https://github.com/kobaltedev/kobalte/commit/4dc7f8df7167f0da8f6abd4c402a771d831cecb3 "feat: support string style prop (#432) * chore: update formatter") | 2 years agoJun 18, 2024 |
| [alert](https://github.com/kobaltedev/kobalte/tree/main/packages/core/src/alert "alert") | [alert](https://github.com/kobaltedev/kobalte/tree/main/packages/core/src/alert "alert") | [feat: support string style prop (](https://github.com/kobaltedev/kobalte/commit/4dc7f8df7167f0da8f6abd4c402a771d831cecb3 "feat: support string style prop (#432) * chore: update formatter") [#432](https://github.com/kobaltedev/kobalte/pull/432) [)](https://github.com/kobaltedev/kobalte/commit/4dc7f8df7167f0da8f6abd4c402a771d831cecb3 "feat: support string style prop (#432) * chore: update formatter") | 2 years agoJun 18, 2024 |
| [badge](https://github.com/kobaltedev/kobalte/tree/main/packages/core/src/badge "badge") | [badge](https://github.com/kobaltedev/kobalte/tree/main/packages/core/src/badge "badge") | [refactor(FileField): rename from FileUpload and change types (](https://github.com/kobaltedev/kobalte/commit/34bd063d55bef45fb1eca87b2b801db367f7fb93 "refactor(FileField): rename from FileUpload and change types (#552)") [#552](https://github.com/kobaltedev/kobalte/pull/552) [)](https://github.com/kobaltedev/kobalte/commit/34bd063d55bef45fb1eca87b2b801db367f7fb93 "refactor(FileField): rename from FileUpload and change types (#552)") | last yearFeb 6, 2025 |
| [breadcrumbs](https://github.com/kobaltedev/kobalte/tree/main/packages/core/src/breadcrumbs "breadcrumbs") | [breadcrumbs](https://github.com/kobaltedev/kobalte/tree/main/packages/core/src/breadcrumbs "breadcrumbs") | [refactor: menu radio types and segmented control (](https://github.com/kobaltedev/kobalte/commit/6d1737b17f43994adbc56616408f2f8319d3c521 "refactor: menu radio types and segmented control (#596)") [#596](https://github.com/kobaltedev/kobalte/pull/596) [)](https://github.com/kobaltedev/kobalte/commit/6d1737b17f43994adbc56616408f2f8319d3c521 "refactor: menu radio types and segmented control (#596)") | 10 months agoMay 29, 2025 |
| [button](https://github.com/kobaltedev/kobalte/tree/main/packages/core/src/button "button") | [button](https://github.com/kobaltedev/kobalte/tree/main/packages/core/src/button "button") | [feat: support string style prop (](https://github.com/kobaltedev/kobalte/commit/4dc7f8df7167f0da8f6abd4c402a771d831cecb3 "feat: support string style prop (#432) * chore: update formatter") [#432](https://github.com/kobaltedev/kobalte/pull/432) [)](https://github.com/kobaltedev/kobalte/commit/4dc7f8df7167f0da8f6abd4c402a771d831cecb3 "feat: support string style prop (#432) * chore: update formatter") | 2 years agoJun 18, 2024 |
| [checkbox](https://github.com/kobaltedev/kobalte/tree/main/packages/core/src/checkbox "checkbox") | [checkbox](https://github.com/kobaltedev/kobalte/tree/main/packages/core/src/checkbox "checkbox") | [refactor: menu radio types and segmented control (](https://github.com/kobaltedev/kobalte/commit/6d1737b17f43994adbc56616408f2f8319d3c521 "refactor: menu radio types and segmented control (#596)") [#596](https://github.com/kobaltedev/kobalte/pull/596) [)](https://github.com/kobaltedev/kobalte/commit/6d1737b17f43994adbc56616408f2f8319d3c521 "refactor: menu radio types and segmented control (#596)") | 10 months agoMay 29, 2025 |
| [collapsible](https://github.com/kobaltedev/kobalte/tree/main/packages/core/src/collapsible "collapsible") | [collapsible](https://github.com/kobaltedev/kobalte/tree/main/packages/core/src/collapsible "collapsible") | [refactor: menu radio types and segmented control (](https://github.com/kobaltedev/kobalte/commit/6d1737b17f43994adbc56616408f2f8319d3c521 "refactor: menu radio types and segmented control (#596)") [#596](https://github.com/kobaltedev/kobalte/pull/596) [)](https://github.com/kobaltedev/kobalte/commit/6d1737b17f43994adbc56616408f2f8319d3c521 "refactor: menu radio types and segmented control (#596)") | 10 months agoMay 29, 2025 |
| [color-area](https://github.com/kobaltedev/kobalte/tree/main/packages/core/src/color-area "color-area") | [color-area](https://github.com/kobaltedev/kobalte/tree/main/packages/core/src/color-area "color-area") | [fix(color-area): double onChange trigger (](https://github.com/kobaltedev/kobalte/commit/e1c3385acf3321acdc0c9e57af9d182455c5dcb0 "fix(color-area): double onChange trigger (#593)") [#593](https://github.com/kobaltedev/kobalte/pull/593) [)](https://github.com/kobaltedev/kobalte/commit/e1c3385acf3321acdc0c9e57af9d182455c5dcb0 "fix(color-area): double onChange trigger (#593)") | 8 months agoJul 26, 2025 |
| [color-channel-field](https://github.com/kobaltedev/kobalte/tree/main/packages/core/src/color-channel-field "color-channel-field") | [color-channel-field](https://github.com/kobaltedev/kobalte/tree/main/packages/core/src/color-channel-field "color-channel-field") | [chore: prepare release](https://github.com/kobaltedev/kobalte/commit/91a41e91f45bc5c57bd942ac641137e7745d22a5 "chore: prepare release `0.13.11` (#614)")`0.13.11` [(](https://github.com/kobaltedev/kobalte/commit/91a41e91f45bc5c57bd942ac641137e7745d22a5 "chore: prepare release `0.13.11` (#614)") [#614](https://github.com/kobaltedev/kobalte/pull/614) [)](https://github.com/kobaltedev/kobalte/commit/91a41e91f45bc5c57bd942ac641137e7745d22a5 "chore: prepare release `0.13.11` (#614)") | 8 months agoJul 26, 2025 |
| [color-field](https://github.com/kobaltedev/kobalte/tree/main/packages/core/src/color-field "color-field") | [color-field](https://github.com/kobaltedev/kobalte/tree/main/packages/core/src/color-field "color-field") | [refactor: menu radio types and segmented control (](https://github.com/kobaltedev/kobalte/commit/6d1737b17f43994adbc56616408f2f8319d3c521 "refactor: menu radio types and segmented control (#596)") [#596](https://github.com/kobaltedev/kobalte/pull/596) [)](https://github.com/kobaltedev/kobalte/commit/6d1737b17f43994adbc56616408f2f8319d3c521 "refactor: menu radio types and segmented control (#596)") | 10 months agoMay 29, 2025 |
| [color-mode](https://github.com/kobaltedev/kobalte/tree/main/packages/core/src/color-mode "color-mode") | [color-mode](https://github.com/kobaltedev/kobalte/tree/main/packages/core/src/color-mode "color-mode") | [feat: support string style prop (](https://github.com/kobaltedev/kobalte/commit/4dc7f8df7167f0da8f6abd4c402a771d831cecb3 "feat: support string style prop (#432) * chore: update formatter") [#432](https://github.com/kobaltedev/kobalte/pull/432) [)](https://github.com/kobaltedev/kobalte/commit/4dc7f8df7167f0da8f6abd4c402a771d831cecb3 "feat: support string style prop (#432) * chore: update formatter") | 2 years agoJun 18, 2024 |
| [color-slider](https://github.com/kobaltedev/kobalte/tree/main/packages/core/src/color-slider "color-slider") | [color-slider](https://github.com/kobaltedev/kobalte/tree/main/packages/core/src/color-slider "color-slider") | [refactor: menu radio types and segmented control (](https://github.com/kobaltedev/kobalte/commit/6d1737b17f43994adbc56616408f2f8319d3c521 "refactor: menu radio types and segmented control (#596)") [#596](https://github.com/kobaltedev/kobalte/pull/596) [)](https://github.com/kobaltedev/kobalte/commit/6d1737b17f43994adbc56616408f2f8319d3c521 "refactor: menu radio types and segmented control (#596)") | 10 months agoMay 29, 2025 |
| [color-swatch](https://github.com/kobaltedev/kobalte/tree/main/packages/core/src/color-swatch "color-swatch") | [color-swatch](https://github.com/kobaltedev/kobalte/tree/main/packages/core/src/color-swatch "color-swatch") | [refactor: move new component files (](https://github.com/kobaltedev/kobalte/commit/5312ce79d82e4a4b4723050c9712eb23169b9ad3 "refactor: move new component files (#513)") [#513](https://github.com/kobaltedev/kobalte/pull/513) [)](https://github.com/kobaltedev/kobalte/commit/5312ce79d82e4a4b4723050c9712eb23169b9ad3 "refactor: move new component files (#513)") | 2 years agoNov 2, 2024 |
| [color-wheel](https://github.com/kobaltedev/kobalte/tree/main/packages/core/src/color-wheel "color-wheel") | [color-wheel](https://github.com/kobaltedev/kobalte/tree/main/packages/core/src/color-wheel "color-wheel") | [refactor: menu radio types and segmented control (](https://github.com/kobaltedev/kobalte/commit/6d1737b17f43994adbc56616408f2f8319d3c521 "refactor: menu radio types and segmented control (#596)") [#596](https://github.com/kobaltedev/kobalte/pull/596) [)](https://github.com/kobaltedev/kobalte/commit/6d1737b17f43994adbc56616408f2f8319d3c521 "refactor: menu radio types and segmented control (#596)") | 10 months agoMay 29, 2025 |
| [colors](https://github.com/kobaltedev/kobalte/tree/main/packages/core/src/colors "colors") | [colors](https://github.com/kobaltedev/kobalte/tree/main/packages/core/src/colors "colors") | [chore: prepare release](https://github.com/kobaltedev/kobalte/commit/91a41e91f45bc5c57bd942ac641137e7745d22a5 "chore: prepare release `0.13.11` (#614)")`0.13.11` [(](https://github.com/kobaltedev/kobalte/commit/91a41e91f45bc5c57bd942ac641137e7745d22a5 "chore: prepare release `0.13.11` (#614)") [#614](https://github.com/kobaltedev/kobalte/pull/614) [)](https://github.com/kobaltedev/kobalte/commit/91a41e91f45bc5c57bd942ac641137e7745d22a5 "chore: prepare release `0.13.11` (#614)") | 8 months agoJul 26, 2025 |
| [combobox](https://github.com/kobaltedev/kobalte/tree/main/packages/core/src/combobox "combobox") | [combobox](https://github.com/kobaltedev/kobalte/tree/main/packages/core/src/combobox "combobox") | [refactor: menu radio types and segmented control (](https://github.com/kobaltedev/kobalte/commit/6d1737b17f43994adbc56616408f2f8319d3c521 "refactor: menu radio types and segmented control (#596)") [#596](https://github.com/kobaltedev/kobalte/pull/596) [)](https://github.com/kobaltedev/kobalte/commit/6d1737b17f43994adbc56616408f2f8319d3c521 "refactor: menu radio types and segmented control (#596)") | 10 months agoMay 29, 2025 |
| [context-menu](https://github.com/kobaltedev/kobalte/tree/main/packages/core/src/context-menu "context-menu") | [context-menu](https://github.com/kobaltedev/kobalte/tree/main/packages/core/src/context-menu "context-menu") | [refactor: menu radio types and segmented control (](https://github.com/kobaltedev/kobalte/commit/6d1737b17f43994adbc56616408f2f8319d3c521 "refactor: menu radio types and segmented control (#596)") [#596](https://github.com/kobaltedev/kobalte/pull/596) [)](https://github.com/kobaltedev/kobalte/commit/6d1737b17f43994adbc56616408f2f8319d3c521 "refactor: menu radio types and segmented control (#596)") | 10 months agoMay 29, 2025 |
| [dialog](https://github.com/kobaltedev/kobalte/tree/main/packages/core/src/dialog "dialog") | [dialog](https://github.com/kobaltedev/kobalte/tree/main/packages/core/src/dialog "dialog") | [refactor: menu radio types and segmented control (](https://github.com/kobaltedev/kobalte/commit/6d1737b17f43994adbc56616408f2f8319d3c521 "refactor: menu radio types and segmented control (#596)") [#596](https://github.com/kobaltedev/kobalte/pull/596) [)](https://github.com/kobaltedev/kobalte/commit/6d1737b17f43994adbc56616408f2f8319d3c521 "refactor: menu radio types and segmented control (#596)") | 10 months agoMay 29, 2025 |
| [dismissable-layer](https://github.com/kobaltedev/kobalte/tree/main/packages/core/src/dismissable-layer "dismissable-layer") | [dismissable-layer](https://github.com/kobaltedev/kobalte/tree/main/packages/core/src/dismissable-layer "dismissable-layer") | [feat: support string style prop (](https://github.com/kobaltedev/kobalte/commit/4dc7f8df7167f0da8f6abd4c402a771d831cecb3 "feat: support string style prop (#432) * chore: update formatter") [#432](https://github.com/kobaltedev/kobalte/pull/432) [)](https://github.com/kobaltedev/kobalte/commit/4dc7f8df7167f0da8f6abd4c402a771d831cecb3 "feat: support string style prop (#432) * chore: update formatter") | 2 years agoJun 18, 2024 |
| [dropdown-menu](https://github.com/kobaltedev/kobalte/tree/main/packages/core/src/dropdown-menu "dropdown-menu") | [dropdown-menu](https://github.com/kobaltedev/kobalte/tree/main/packages/core/src/dropdown-menu "dropdown-menu") | [feat: support string style prop (](https://github.com/kobaltedev/kobalte/commit/4dc7f8df7167f0da8f6abd4c402a771d831cecb3 "feat: support string style prop (#432) * chore: update formatter") [#432](https://github.com/kobaltedev/kobalte/pull/432) [)](https://github.com/kobaltedev/kobalte/commit/4dc7f8df7167f0da8f6abd4c402a771d831cecb3 "feat: support string style prop (#432) * chore: update formatter") | 2 years agoJun 18, 2024 |
| [file-field](https://github.com/kobaltedev/kobalte/tree/main/packages/core/src/file-field "file-field") | [file-field](https://github.com/kobaltedev/kobalte/tree/main/packages/core/src/file-field "file-field") | [refactor: menu radio types and segmented control (](https://github.com/kobaltedev/kobalte/commit/6d1737b17f43994adbc56616408f2f8319d3c521 "refactor: menu radio types and segmented control (#596)") [#596](https://github.com/kobaltedev/kobalte/pull/596) [)](https://github.com/kobaltedev/kobalte/commit/6d1737b17f43994adbc56616408f2f8319d3c521 "refactor: menu radio types and segmented control (#596)") | 10 months agoMay 29, 2025 |
| [form-control](https://github.com/kobaltedev/kobalte/tree/main/packages/core/src/form-control "form-control") | [form-control](https://github.com/kobaltedev/kobalte/tree/main/packages/core/src/form-control "form-control") | [feat: support string style prop (](https://github.com/kobaltedev/kobalte/commit/4dc7f8df7167f0da8f6abd4c402a771d831cecb3 "feat: support string style prop (#432) * chore: update formatter") [#432](https://github.com/kobaltedev/kobalte/pull/432) [)](https://github.com/kobaltedev/kobalte/commit/4dc7f8df7167f0da8f6abd4c402a771d831cecb3 "feat: support string style prop (#432) * chore: update formatter") | 2 years agoJun 18, 2024 |
| [hover-card](https://github.com/kobaltedev/kobalte/tree/main/packages/core/src/hover-card "hover-card") | [hover-card](https://github.com/kobaltedev/kobalte/tree/main/packages/core/src/hover-card "hover-card") | [refactor: menu radio types and segmented control (](https://github.com/kobaltedev/kobalte/commit/6d1737b17f43994adbc56616408f2f8319d3c521 "refactor: menu radio types and segmented control (#596)") [#596](https://github.com/kobaltedev/kobalte/pull/596) [)](https://github.com/kobaltedev/kobalte/commit/6d1737b17f43994adbc56616408f2f8319d3c521 "refactor: menu radio types and segmented control (#596)") | 10 months agoMay 29, 2025 |
| [i18n](https://github.com/kobaltedev/kobalte/tree/main/packages/core/src/i18n "i18n") | [i18n](https://github.com/kobaltedev/kobalte/tree/main/packages/core/src/i18n "i18n") | [feat: support string style prop (](https://github.com/kobaltedev/kobalte/commit/4dc7f8df7167f0da8f6abd4c402a771d831cecb3 "feat: support string style prop (#432) * chore: update formatter") [#432](https://github.com/kobaltedev/kobalte/pull/432) [)](https://github.com/kobaltedev/kobalte/commit/4dc7f8df7167f0da8f6abd4c402a771d831cecb3 "feat: support string style prop (#432) * chore: update formatter") | 2 years agoJun 18, 2024 |
| [image](https://github.com/kobaltedev/kobalte/tree/main/packages/core/src/image "image") | [image](https://github.com/kobaltedev/kobalte/tree/main/packages/core/src/image "image") | [fix(image): handle crossOrigin and referrerPolicy (](https://github.com/kobaltedev/kobalte/commit/53a39a8cf11d0ed57743ee8c77e8d52372045b94 "fix(image): handle crossOrigin and referrerPolicy (#598)") [#598](https://github.com/kobaltedev/kobalte/pull/598) [)](https://github.com/kobaltedev/kobalte/commit/53a39a8cf11d0ed57743ee8c77e8d52372045b94 "fix(image): handle crossOrigin and referrerPolicy (#598)") | 8 months agoJul 26, 2025 |
| [link](https://github.com/kobaltedev/kobalte/tree/main/packages/core/src/link "link") | [link](https://github.com/kobaltedev/kobalte/tree/main/packages/core/src/link "link") | [feat: support string style prop (](https://github.com/kobaltedev/kobalte/commit/4dc7f8df7167f0da8f6abd4c402a771d831cecb3 "feat: support string style prop (#432) * chore: update formatter") [#432](https://github.com/kobaltedev/kobalte/pull/432) [)](https://github.com/kobaltedev/kobalte/commit/4dc7f8df7167f0da8f6abd4c402a771d831cecb3 "feat: support string style prop (#432) * chore: update formatter") | 2 years agoJun 18, 2024 |
| [list](https://github.com/kobaltedev/kobalte/tree/main/packages/core/src/list "list") | [list](https://github.com/kobaltedev/kobalte/tree/main/packages/core/src/list "list") | [feat: support string style prop (](https://github.com/kobaltedev/kobalte/commit/4dc7f8df7167f0da8f6abd4c402a771d831cecb3 "feat: support string style prop (#432) * chore: update formatter") [#432](https://github.com/kobaltedev/kobalte/pull/432) [)](https://github.com/kobaltedev/kobalte/commit/4dc7f8df7167f0da8f6abd4c402a771d831cecb3 "feat: support string style prop (#432) * chore: update formatter") | 2 years agoJun 18, 2024 |
| [listbox](https://github.com/kobaltedev/kobalte/tree/main/packages/core/src/listbox "listbox") | [listbox](https://github.com/kobaltedev/kobalte/tree/main/packages/core/src/listbox "listbox") | [refactor: menu radio types and segmented control (](https://github.com/kobaltedev/kobalte/commit/6d1737b17f43994adbc56616408f2f8319d3c521 "refactor: menu radio types and segmented control (#596)") [#596](https://github.com/kobaltedev/kobalte/pull/596) [)](https://github.com/kobaltedev/kobalte/commit/6d1737b17f43994adbc56616408f2f8319d3c521 "refactor: menu radio types and segmented control (#596)") | 10 months agoMay 29, 2025 |
| [live-announcer](https://github.com/kobaltedev/kobalte/tree/main/packages/core/src/live-announcer "live-announcer") | [live-announcer](https://github.com/kobaltedev/kobalte/tree/main/packages/core/src/live-announcer "live-announcer") | [chore: strip license headers from build (](https://github.com/kobaltedev/kobalte/commit/f191fe728cf17df414d0fb2bd433a34c5b3a659c "chore: strip license headers from build (#403)") [#403](https://github.com/kobaltedev/kobalte/pull/403) [)](https://github.com/kobaltedev/kobalte/commit/f191fe728cf17df414d0fb2bd433a34c5b3a659c "chore: strip license headers from build (#403)") | 2 years agoMay 3, 2024 |
| [menu](https://github.com/kobaltedev/kobalte/tree/main/packages/core/src/menu "menu") | [menu](https://github.com/kobaltedev/kobalte/tree/main/packages/core/src/menu "menu") | [fix(menu): fix navigationmenu operation on mobile (](https://github.com/kobaltedev/kobalte/commit/0f5b98e14a9cbbeb4e66db90560282b667dd4a32 "fix(menu): fix navigationmenu operation on mobile (#634)") [#634](https://github.com/kobaltedev/kobalte/pull/634) [)](https://github.com/kobaltedev/kobalte/commit/0f5b98e14a9cbbeb4e66db90560282b667dd4a32 "fix(menu): fix navigationmenu operation on mobile (#634)") | 5 months agoOct 1, 2025 |
| [menubar](https://github.com/kobaltedev/kobalte/tree/main/packages/core/src/menubar "menubar") | [menubar](https://github.com/kobaltedev/kobalte/tree/main/packages/core/src/menubar "menubar") | [refactor: menu radio types and segmented control (](https://github.com/kobaltedev/kobalte/commit/6d1737b17f43994adbc56616408f2f8319d3c521 "refactor: menu radio types and segmented control (#596)") [#596](https://github.com/kobaltedev/kobalte/pull/596) [)](https://github.com/kobaltedev/kobalte/commit/6d1737b17f43994adbc56616408f2f8319d3c521 "refactor: menu radio types and segmented control (#596)") | 10 months agoMay 29, 2025 |
| [meter](https://github.com/kobaltedev/kobalte/tree/main/packages/core/src/meter "meter") | [meter](https://github.com/kobaltedev/kobalte/tree/main/packages/core/src/meter "meter") | [fix(meter, progress): remove rounding from fill width (](https://github.com/kobaltedev/kobalte/commit/82923280705eaa16943824080424537c9770e358 "fix(meter, progress): remove rounding from fill width (#602)") [#602](https://github.com/kobaltedev/kobalte/pull/602) [)](https://github.com/kobaltedev/kobalte/commit/82923280705eaa16943824080424537c9770e358 "fix(meter, progress): remove rounding from fill width (#602)") | 8 months agoJul 26, 2025 |
| [navigation-menu](https://github.com/kobaltedev/kobalte/tree/main/packages/core/src/navigation-menu "navigation-menu") | [navigation-menu](https://github.com/kobaltedev/kobalte/tree/main/packages/core/src/navigation-menu "navigation-menu") | [refactor: menu radio types and segmented control (](https://github.com/kobaltedev/kobalte/commit/6d1737b17f43994adbc56616408f2f8319d3c521 "refactor: menu radio types and segmented control (#596)") [#596](https://github.com/kobaltedev/kobalte/pull/596) [)](https://github.com/kobaltedev/kobalte/commit/6d1737b17f43994adbc56616408f2f8319d3c521 "refactor: menu radio types and segmented control (#596)") | 10 months agoMay 29, 2025 |
| [number-field](https://github.com/kobaltedev/kobalte/tree/main/packages/core/src/number-field "number-field") | [number-field](https://github.com/kobaltedev/kobalte/tree/main/packages/core/src/number-field "number-field") | [refactor: menu radio types and segmented control (](https://github.com/kobaltedev/kobalte/commit/6d1737b17f43994adbc56616408f2f8319d3c521 "refactor: menu radio types and segmented control (#596)") [#596](https://github.com/kobaltedev/kobalte/pull/596) [)](https://github.com/kobaltedev/kobalte/commit/6d1737b17f43994adbc56616408f2f8319d3c521 "refactor: menu radio types and segmented control (#596)") | 10 months agoMay 29, 2025 |
| [pagination](https://github.com/kobaltedev/kobalte/tree/main/packages/core/src/pagination "pagination") | [pagination](https://github.com/kobaltedev/kobalte/tree/main/packages/core/src/pagination "pagination") | [refactor: menu radio types and segmented control (](https://github.com/kobaltedev/kobalte/commit/6d1737b17f43994adbc56616408f2f8319d3c521 "refactor: menu radio types and segmented control (#596)") [#596](https://github.com/kobaltedev/kobalte/pull/596) [)](https://github.com/kobaltedev/kobalte/commit/6d1737b17f43994adbc56616408f2f8319d3c521 "refactor: menu radio types and segmented control (#596)") | 10 months agoMay 29, 2025 |
| [polymorphic](https://github.com/kobaltedev/kobalte/tree/main/packages/core/src/polymorphic "polymorphic") | [polymorphic](https://github.com/kobaltedev/kobalte/tree/main/packages/core/src/polymorphic "polymorphic") | [fix(polymorphic): override the "component" prop of](https://github.com/kobaltedev/kobalte/commit/5ef35cfbd77ea462035cc56353ebeec2e60ca995 "fix(polymorphic): override the \"component\" prop of `<Dynamic />` (#437)")`<Dynamic />` [(](https://github.com/kobaltedev/kobalte/commit/5ef35cfbd77ea462035cc56353ebeec2e60ca995 "fix(polymorphic): override the \"component\" prop of `<Dynamic />` (#437)") [#437](https://github.com/kobaltedev/kobalte/pull/437) [)](https://github.com/kobaltedev/kobalte/commit/5ef35cfbd77ea462035cc56353ebeec2e60ca995 "fix(polymorphic): override the \"component\" prop of `<Dynamic />` (#437)") | 2 years agoJun 28, 2024 |
| [popover](https://github.com/kobaltedev/kobalte/tree/main/packages/core/src/popover "popover") | [popover](https://github.com/kobaltedev/kobalte/tree/main/packages/core/src/popover "popover") | [refactor: menu radio types and segmented control (](https://github.com/kobaltedev/kobalte/commit/6d1737b17f43994adbc56616408f2f8319d3c521 "refactor: menu radio types and segmented control (#596)") [#596](https://github.com/kobaltedev/kobalte/pull/596) [)](https://github.com/kobaltedev/kobalte/commit/6d1737b17f43994adbc56616408f2f8319d3c521 "refactor: menu radio types and segmented control (#596)") | 10 months agoMay 29, 2025 |
| [popper](https://github.com/kobaltedev/kobalte/tree/main/packages/core/src/popper "popper") | [popper](https://github.com/kobaltedev/kobalte/tree/main/packages/core/src/popper "popper") | [refactor: menu radio types and segmented control (](https://github.com/kobaltedev/kobalte/commit/6d1737b17f43994adbc56616408f2f8319d3c521 "refactor: menu radio types and segmented control (#596)") [#596](https://github.com/kobaltedev/kobalte/pull/596) [)](https://github.com/kobaltedev/kobalte/commit/6d1737b17f43994adbc56616408f2f8319d3c521 "refactor: menu radio types and segmented control (#596)") | 10 months agoMay 29, 2025 |
| [primitives](https://github.com/kobaltedev/kobalte/tree/main/packages/core/src/primitives "primitives") | [primitives](https://github.com/kobaltedev/kobalte/tree/main/packages/core/src/primitives "primitives") | [chore: removed unused imports (](https://github.com/kobaltedev/kobalte/commit/2d05356cecf7e189034f1f94f9f92f63cce216de "chore: removed unused imports (#629)") [#629](https://github.com/kobaltedev/kobalte/pull/629) [)](https://github.com/kobaltedev/kobalte/commit/2d05356cecf7e189034f1f94f9f92f63cce216de "chore: removed unused imports (#629)") | 5 months agoOct 1, 2025 |
| [progress](https://github.com/kobaltedev/kobalte/tree/main/packages/core/src/progress "progress") | [progress](https://github.com/kobaltedev/kobalte/tree/main/packages/core/src/progress "progress") | [fix(meter, progress): remove rounding from fill width (](https://github.com/kobaltedev/kobalte/commit/82923280705eaa16943824080424537c9770e358 "fix(meter, progress): remove rounding from fill width (#602)") [#602](https://github.com/kobaltedev/kobalte/pull/602) [)](https://github.com/kobaltedev/kobalte/commit/82923280705eaa16943824080424537c9770e358 "fix(meter, progress): remove rounding from fill width (#602)") | 8 months agoJul 26, 2025 |
| [radio-group](https://github.com/kobaltedev/kobalte/tree/main/packages/core/src/radio-group "radio-group") | [radio-group](https://github.com/kobaltedev/kobalte/tree/main/packages/core/src/radio-group "radio-group") | [refactor: menu radio types and segmented control (](https://github.com/kobaltedev/kobalte/commit/6d1737b17f43994adbc56616408f2f8319d3c521 "refactor: menu radio types and segmented control (#596)") [#596](https://github.com/kobaltedev/kobalte/pull/596) [)](https://github.com/kobaltedev/kobalte/commit/6d1737b17f43994adbc56616408f2f8319d3c521 "refactor: menu radio types and segmented control (#596)") | 10 months agoMay 29, 2025 |
| [rating-group](https://github.com/kobaltedev/kobalte/tree/main/packages/core/src/rating-group "rating-group") | [rating-group](https://github.com/kobaltedev/kobalte/tree/main/packages/core/src/rating-group "rating-group") | [feat: RatingGroup component (](https://github.com/kobaltedev/kobalte/commit/0e7656715057106aa594d20d7f614325e63a658c "feat: RatingGroup component (#516) Co-authored-by: HBS999 <mrbirb007@gmail.com> Co-authored-by: jer3m01 <jer3m01@jer3m01.com>") [#516](https://github.com/kobaltedev/kobalte/pull/516) [)](https://github.com/kobaltedev/kobalte/commit/0e7656715057106aa594d20d7f614325e63a658c "feat: RatingGroup component (#516) Co-authored-by: HBS999 <mrbirb007@gmail.com> Co-authored-by: jer3m01 <jer3m01@jer3m01.com>") | 8 months agoJul 26, 2025 |
| [search](https://github.com/kobaltedev/kobalte/tree/main/packages/core/src/search "search") | [search](https://github.com/kobaltedev/kobalte/tree/main/packages/core/src/search "search") | [refactor: menu radio types and segmented control (](https://github.com/kobaltedev/kobalte/commit/6d1737b17f43994adbc56616408f2f8319d3c521 "refactor: menu radio types and segmented control (#596)") [#596](https://github.com/kobaltedev/kobalte/pull/596) [)](https://github.com/kobaltedev/kobalte/commit/6d1737b17f43994adbc56616408f2f8319d3c521 "refactor: menu radio types and segmented control (#596)") | 10 months agoMay 29, 2025 |
| [segmented-control](https://github.com/kobaltedev/kobalte/tree/main/packages/core/src/segmented-control "segmented-control") | [segmented-control](https://github.com/kobaltedev/kobalte/tree/main/packages/core/src/segmented-control "segmented-control") | [refactor: menu radio types and segmented control (](https://github.com/kobaltedev/kobalte/commit/6d1737b17f43994adbc56616408f2f8319d3c521 "refactor: menu radio types and segmented control (#596)") [#596](https://github.com/kobaltedev/kobalte/pull/596) [)](https://github.com/kobaltedev/kobalte/commit/6d1737b17f43994adbc56616408f2f8319d3c521 "refactor: menu radio types and segmented control (#596)") | 10 months agoMay 29, 2025 |
| [select](https://github.com/kobaltedev/kobalte/tree/main/packages/core/src/select "select") | [select](https://github.com/kobaltedev/kobalte/tree/main/packages/core/src/select "select") | [refactor: menu radio types and segmented control (](https://github.com/kobaltedev/kobalte/commit/6d1737b17f43994adbc56616408f2f8319d3c521 "refactor: menu radio types and segmented control (#596)") [#596](https://github.com/kobaltedev/kobalte/pull/596) [)](https://github.com/kobaltedev/kobalte/commit/6d1737b17f43994adbc56616408f2f8319d3c521 "refactor: menu radio types and segmented control (#596)") | 10 months agoMay 29, 2025 |
| [selection](https://github.com/kobaltedev/kobalte/tree/main/packages/core/src/selection "selection") | [selection](https://github.com/kobaltedev/kobalte/tree/main/packages/core/src/selection "selection") | [feat: support string style prop (](https://github.com/kobaltedev/kobalte/commit/4dc7f8df7167f0da8f6abd4c402a771d831cecb3 "feat: support string style prop (#432) * chore: update formatter") [#432](https://github.com/kobaltedev/kobalte/pull/432) [)](https://github.com/kobaltedev/kobalte/commit/4dc7f8df7167f0da8f6abd4c402a771d831cecb3 "feat: support string style prop (#432) * chore: update formatter") | 2 years agoJun 18, 2024 |
| [separator](https://github.com/kobaltedev/kobalte/tree/main/packages/core/src/separator "separator") | [separator](https://github.com/kobaltedev/kobalte/tree/main/packages/core/src/separator "separator") | [feat: support string style prop (](https://github.com/kobaltedev/kobalte/commit/4dc7f8df7167f0da8f6abd4c402a771d831cecb3 "feat: support string style prop (#432) * chore: update formatter") [#432](https://github.com/kobaltedev/kobalte/pull/432) [)](https://github.com/kobaltedev/kobalte/commit/4dc7f8df7167f0da8f6abd4c402a771d831cecb3 "feat: support string style prop (#432) * chore: update formatter") | 2 years agoJun 18, 2024 |
| [skeleton](https://github.com/kobaltedev/kobalte/tree/main/packages/core/src/skeleton "skeleton") | [skeleton](https://github.com/kobaltedev/kobalte/tree/main/packages/core/src/skeleton "skeleton") | [fix: various bug fixes (](https://github.com/kobaltedev/kobalte/commit/24c5ef85a93b66b634c87c5a971ebe4cd72d662b "fix: various bug fixes (#456) * fix(number-field): only format when enabled * fix(number-field): don't trigger `onRawValueChange` on mount when NaN * fix(select): correct type definition & empty value for multiselect * fix(text-field): clear input when controlled value set to undefined * fix(combobox): correct type definition & empty value for multiselect * fix(skeleton): correct data-animate & data-visible attribute value * fix(combobox): close list on outside click * fix(navigation-menu): incorrect animation after closed") [#456](https://github.com/kobaltedev/kobalte/pull/456) [)](https://github.com/kobaltedev/kobalte/commit/24c5ef85a93b66b634c87c5a971ebe4cd72d662b "fix: various bug fixes (#456) * fix(number-field): only format when enabled * fix(number-field): don't trigger `onRawValueChange` on mount when NaN * fix(select): correct type definition & empty value for multiselect * fix(text-field): clear input when controlled value set to undefined * fix(combobox): correct type definition & empty value for multiselect * fix(skeleton): correct data-animate & data-visible attribute value * fix(combobox): close list on outside click * fix(navigation-menu): incorrect animation after closed") | 2 years agoAug 26, 2024 |
| [slider](https://github.com/kobaltedev/kobalte/tree/main/packages/core/src/slider "slider") | [slider](https://github.com/kobaltedev/kobalte/tree/main/packages/core/src/slider "slider") | [refactor: menu radio types and segmented control (](https://github.com/kobaltedev/kobalte/commit/6d1737b17f43994adbc56616408f2f8319d3c521 "refactor: menu radio types and segmented control (#596)") [#596](https://github.com/kobaltedev/kobalte/pull/596) [)](https://github.com/kobaltedev/kobalte/commit/6d1737b17f43994adbc56616408f2f8319d3c521 "refactor: menu radio types and segmented control (#596)") | 10 months agoMay 29, 2025 |
| [spin-button](https://github.com/kobaltedev/kobalte/tree/main/packages/core/src/spin-button "spin-button") | [spin-button](https://github.com/kobaltedev/kobalte/tree/main/packages/core/src/spin-button "spin-button") | [refactor: Search and Color components (](https://github.com/kobaltedev/kobalte/commit/19a0181639d4628f7827dd8a68289ea9aa01ee6e "refactor: Search and Color components (#557)") [#557](https://github.com/kobaltedev/kobalte/pull/557) [)](https://github.com/kobaltedev/kobalte/commit/19a0181639d4628f7827dd8a68289ea9aa01ee6e "refactor: Search and Color components (#557)") | last yearFeb 21, 2025 |
| [switch](https://github.com/kobaltedev/kobalte/tree/main/packages/core/src/switch "switch") | [switch](https://github.com/kobaltedev/kobalte/tree/main/packages/core/src/switch "switch") | [refactor: menu radio types and segmented control (](https://github.com/kobaltedev/kobalte/commit/6d1737b17f43994adbc56616408f2f8319d3c521 "refactor: menu radio types and segmented control (#596)") [#596](https://github.com/kobaltedev/kobalte/pull/596) [)](https://github.com/kobaltedev/kobalte/commit/6d1737b17f43994adbc56616408f2f8319d3c521 "refactor: menu radio types and segmented control (#596)") | 10 months agoMay 29, 2025 |
| [tabs](https://github.com/kobaltedev/kobalte/tree/main/packages/core/src/tabs "tabs") | [tabs](https://github.com/kobaltedev/kobalte/tree/main/packages/core/src/tabs "tabs") | [refactor: menu radio types and segmented control (](https://github.com/kobaltedev/kobalte/commit/6d1737b17f43994adbc56616408f2f8319d3c521 "refactor: menu radio types and segmented control (#596)") [#596](https://github.com/kobaltedev/kobalte/pull/596) [)](https://github.com/kobaltedev/kobalte/commit/6d1737b17f43994adbc56616408f2f8319d3c521 "refactor: menu radio types and segmented control (#596)") | 10 months agoMay 29, 2025 |
| [text-field](https://github.com/kobaltedev/kobalte/tree/main/packages/core/src/text-field "text-field") | [text-field](https://github.com/kobaltedev/kobalte/tree/main/packages/core/src/text-field "text-field") | [refactor: menu radio types and segmented control (](https://github.com/kobaltedev/kobalte/commit/6d1737b17f43994adbc56616408f2f8319d3c521 "refactor: menu radio types and segmented control (#596)") [#596](https://github.com/kobaltedev/kobalte/pull/596) [)](https://github.com/kobaltedev/kobalte/commit/6d1737b17f43994adbc56616408f2f8319d3c521 "refactor: menu radio types and segmented control (#596)") | 10 months agoMay 29, 2025 |
| [time-field](https://github.com/kobaltedev/kobalte/tree/main/packages/core/src/time-field "time-field") | [time-field](https://github.com/kobaltedev/kobalte/tree/main/packages/core/src/time-field "time-field") | [feat: TimeField component (](https://github.com/kobaltedev/kobalte/commit/5a48be9b665e9a6afe0c655407e95b2fadc72882 "feat: TimeField component (#529) Co-authored-by: HBS999 <mrbirb007@gmail.com> Co-authored-by: jer3m01 <jer3m01@jer3m01.com>") [#529](https://github.com/kobaltedev/kobalte/pull/529) [)](https://github.com/kobaltedev/kobalte/commit/5a48be9b665e9a6afe0c655407e95b2fadc72882 "feat: TimeField component (#529) Co-authored-by: HBS999 <mrbirb007@gmail.com> Co-authored-by: jer3m01 <jer3m01@jer3m01.com>") | 8 months agoJul 26, 2025 |
| [toast](https://github.com/kobaltedev/kobalte/tree/main/packages/core/src/toast "toast") | [toast](https://github.com/kobaltedev/kobalte/tree/main/packages/core/src/toast "toast") | [refactor: menu radio types and segmented control (](https://github.com/kobaltedev/kobalte/commit/6d1737b17f43994adbc56616408f2f8319d3c521 "refactor: menu radio types and segmented control (#596)") [#596](https://github.com/kobaltedev/kobalte/pull/596) [)](https://github.com/kobaltedev/kobalte/commit/6d1737b17f43994adbc56616408f2f8319d3c521 "refactor: menu radio types and segmented control (#596)") | 10 months agoMay 29, 2025 |
| [toggle-button](https://github.com/kobaltedev/kobalte/tree/main/packages/core/src/toggle-button "toggle-button") | [toggle-button](https://github.com/kobaltedev/kobalte/tree/main/packages/core/src/toggle-button "toggle-button") | [feat: support string style prop (](https://github.com/kobaltedev/kobalte/commit/4dc7f8df7167f0da8f6abd4c402a771d831cecb3 "feat: support string style prop (#432) * chore: update formatter") [#432](https://github.com/kobaltedev/kobalte/pull/432) [)](https://github.com/kobaltedev/kobalte/commit/4dc7f8df7167f0da8f6abd4c402a771d831cecb3 "feat: support string style prop (#432) * chore: update formatter") | 2 years agoJun 18, 2024 |
| [toggle-group](https://github.com/kobaltedev/kobalte/tree/main/packages/core/src/toggle-group "toggle-group") | [toggle-group](https://github.com/kobaltedev/kobalte/tree/main/packages/core/src/toggle-group "toggle-group") | [fix(toggle-group): reactive disabled state (](https://github.com/kobaltedev/kobalte/commit/631ea73ab2444a857a82889ee639e0c02833b234 "fix(toggle-group): reactive disabled state (#595)") [#595](https://github.com/kobaltedev/kobalte/pull/595) [)](https://github.com/kobaltedev/kobalte/commit/631ea73ab2444a857a82889ee639e0c02833b234 "fix(toggle-group): reactive disabled state (#595)") | 8 months agoJul 26, 2025 |
| [tooltip](https://github.com/kobaltedev/kobalte/tree/main/packages/core/src/tooltip "tooltip") | [tooltip](https://github.com/kobaltedev/kobalte/tree/main/packages/core/src/tooltip "tooltip") | [feat(tooltip): expose onCurrentPlacementChange (](https://github.com/kobaltedev/kobalte/commit/8a2d219b442fb5febb7221ef3e685545076b544e "feat(tooltip): expose onCurrentPlacementChange (#637)") [#637](https://github.com/kobaltedev/kobalte/pull/637) [)](https://github.com/kobaltedev/kobalte/commit/8a2d219b442fb5febb7221ef3e685545076b544e "feat(tooltip): expose onCurrentPlacementChange (#637)") | 3 months agoDec 16, 2025 |
| [index.tsx](https://github.com/kobaltedev/kobalte/blob/main/packages/core/src/index.tsx "index.tsx") | [index.tsx](https://github.com/kobaltedev/kobalte/blob/main/packages/core/src/index.tsx "index.tsx") | [feat: TimeField component (](https://github.com/kobaltedev/kobalte/commit/5a48be9b665e9a6afe0c655407e95b2fadc72882 "feat: TimeField component (#529) Co-authored-by: HBS999 <mrbirb007@gmail.com> Co-authored-by: jer3m01 <jer3m01@jer3m01.com>") [#529](https://github.com/kobaltedev/kobalte/pull/529) [)](https://github.com/kobaltedev/kobalte/commit/5a48be9b665e9a6afe0c655407e95b2fadc72882 "feat: TimeField component (#529) Co-authored-by: HBS999 <mrbirb007@gmail.com> Co-authored-by: jer3m01 <jer3m01@jer3m01.com>") | 8 months agoJul 26, 2025 |
| View all files |
You cant perform that action at this time.

View File

@ -1,29 +0,0 @@
# Introduction
Kobalte is a UI toolkit for building accessible web apps and design systems with SolidJS. It provides a set of low-level UI components and primitives which can be the foundation for your design system implementation.
## Key features
### Accessible
Components follow the [WAI-ARIA Authoring Practices](https://www.w3.org/WAI/ARIA/apg/) whenever possible. Kobalte handle accessibility implementation details like ARIA attributes, focus management, and keyboard navigation.
### Composable
Kobalte provides granular access to each component parts, so you can wrap them and add your own event listeners, props, etc.
### Unstyled
Components are shipped with zero styles, allowing you to completely customize the look and feel. Bring your preferred styling solution (vanilla CSS, Tailwind, CSS-in-JS libraries, etc...).
## Acknowledgment
Kobalte would not have been possible without the prior art done by other meaningful projects from the frontend community including:
- Ariakit - [https://ariakit.org/](https://ariakit.org/)
- Radix UI - [https://www.radix-ui.com/](https://www.radix-ui.com/)
- React Aria - [https://react-spectrum.adobe.com/react-aria/](https://react-spectrum.adobe.com/react-aria/)
- Zag - [https://zagjs.com/](https://zagjs.com/)
- corvu - [https://corvu.dev/](https://corvu.dev/)
Next[Getting started→](https://kobalte.dev/docs/core/overview/getting-started)

View File

@ -0,0 +1,936 @@
# Menubar
A visually persistent menu common in desktop applications that provides quick access to a consistent set of commands.
## Import
```
Copyts
import { Menubar } from "@kobalte/core/menubar";
// or
import { Root, Menu, ... } from "@kobalte/core/menubar";
// or (deprecated)
import { Menubar } from "@kobalte/core";
```
```
Copyts
import { Menubar } from "@kobalte/core/menubar";
// or
import { Root, Menu, ... } from "@kobalte/core/menubar";
// or (deprecated)
import { Menubar } from "@kobalte/core";
```
## Features
- Follows the [WAI ARIA Menubar](https://www.w3.org/WAI/ARIA/apg/patterns/menubar/) design pattern.
- Supports modal and non-modal modes.
- Supports submenus.
- Supports items, labels, groups of items.
- Supports checkable items (single or multiple) with optional indeterminate state.
- Support disabled items.
- Complex item labeling support for accessibility.
- Keyboard opening and navigation support.
- Automatic scrolling support during keyboard navigation.
- Typeahead to allow focusing items by typing text.
- Optionally render a pointing arrow.
- Focus is fully managed.
## Anatomy
The menubar consists of:
- **Menubar:** The root container for a menubar.
The menu consists of:
- **Menubar.Menu:** The container of each menu.
- **Menubar.Trigger:** The button that toggles the menu.
- **Menubar.Icon:** A small icon that can be displayed inside the menu trigger as a visual affordance for the fact it can be open.
- **Menubar.Portal:** Portals its children into the `body` when the menu is open.
- **Menubar.Content:** Contains the content to be rendered when the menu is open.
- **Menubar.Arrow:** An optional arrow element to render alongside the menu content.
- **Menubar.Separator:** Used to visually separate items in the menu.
- **Menubar.Group:** Used to group multiple items. Use in conjunction with `Menubar.GroupLabel` to ensure good accessibility via automatic labelling.
- **Menubar.GroupLabel:** Used to render the label of a group. It won't be focusable using arrow keys.
- **Menubar.Sub:** Contains all the parts of a submenu.
- **Menubar.SubTrigger:** An item that opens a submenu. Must be rendered inside `Menubar.Sub`.
- **Menubar.SubContent:** The component that pops out when a submenu is open. Must be rendered inside `Menubar.Sub`.
The menu item consists of:
- **Menubar.Item:** An item of the select.
- **Menubar.ItemLabel:** An accessible label to be announced for the item.
- **Menubar.ItemDescription:** An optional accessible description to be announced for the item.
- **Menubar.ItemIndicator:** The visual indicator rendered when the item is checked.
The checkable menu item consists of:
- **Menubar.RadioGroup:** Used to group multiple `Menubar.RadioItem`s and manage the selection.
- **Menubar.RadioItem:** An item that can be controlled and rendered like a radio.
- **Menubar.CheckboxItem:** An item that can be controlled and rendered like a checkbox.
```
Copytsx
<Menubar>
<Menubar.Menu>
<Menubar.Trigger>
<Menubar.Icon />
</Menubar.Trigger>
<Menubar.Portal>
<Menubar.Content>
<Menubar.Arrow />
<Menubar.Item>
<Menubar.ItemLabel />
<Menubar.ItemDescription />
</Menubar.Item>
<Menubar.Group>
<Menubar.GroupLabel />
<Menubar.Item />
</Menubar.Group>
<Menubar.CheckboxItem>
<Menubar.ItemIndicator />
</Menubar.CheckboxItem>
<Menubar.RadioGroup>
<Menubar.RadioItem>
<Menubar.ItemIndicator />
</Menubar.RadioItem>
</Menubar.RadioGroup>
<Menubar.Sub>
<Menubar.SubTrigger />
<Menubar.Portal>
<Menubar.SubContent />
</Menubar.Portal>
</Menubar.Sub>
<Menubar.Separator />
</Menubar.Content>
</Menubar.Portal>
</Menubar.Menu>
</Menubar>
```
```
Copytsx
<Menubar>
<Menubar.Menu>
<Menubar.Trigger>
<Menubar.Icon />
</Menubar.Trigger>
<Menubar.Portal>
<Menubar.Content>
<Menubar.Arrow />
<Menubar.Item>
<Menubar.ItemLabel />
<Menubar.ItemDescription />
</Menubar.Item>
<Menubar.Group>
<Menubar.GroupLabel />
<Menubar.Item />
</Menubar.Group>
<Menubar.CheckboxItem>
<Menubar.ItemIndicator />
</Menubar.CheckboxItem>
<Menubar.RadioGroup>
<Menubar.RadioItem>
<Menubar.ItemIndicator />
</Menubar.RadioItem>
</Menubar.RadioGroup>
<Menubar.Sub>
<Menubar.SubTrigger />
<Menubar.Portal>
<Menubar.SubContent />
</Menubar.Portal>
</Menubar.Sub>
<Menubar.Separator />
</Menubar.Content>
</Menubar.Portal>
</Menubar.Menu>
</Menubar>
```
## Example
GitFileEdit
index.tsxstyle.css
```
Copytsx
import { Menubar } from "@kobalte/core/menubar";
import { createSignal } from "solid-js";
import { CheckIcon, ChevronRightIcon, DotFilledIcon } from "some-icon-library";
import "./style.css";
function App() {
const [showGitLog, setShowGitLog] = createSignal(true);
const [showHistory, setShowHistory] = createSignal(false);
const [branch, setBranch] = createSignal("main");
return (
<Menubar class="menubar__root">
<Menubar.Menu>
<Menubar.Trigger class="menubar__trigger">
Git
</Menubar.Trigger>
<Menubar.Portal>
<Menubar.Content class="menubar__content">
<Menubar.Item class="menubar__item">
Commit <div class="menubar__item-right-slot">⌘+K</div>
</Menubar.Item>
<Menubar.Item class="menubar__item">
Push <div class="menubar__item-right-slot">⇧+⌘+K</div>
</Menubar.Item>
<Menubar.Item class="menubar__item" disabled>
Update Project <div class="menubar__item-right-slot">⌘+T</div>
</Menubar.Item>
<Menubar.Sub overlap gutter={4} shift={-8}>
<Menubar.SubTrigger class="menubar__sub-trigger">
GitHub
<div class="menubar__item-right-slot">
<ChevronRightIcon width={20} height={20} />
</div>
</Menubar.SubTrigger>
<Menubar.Portal>
<Menubar.SubContent class="menubar__sub-content">
<Menubar.Item class="menubar__item">
Create Pull Request…
</Menubar.Item>
<Menubar.Item class="menubar__item">
View Pull Requests
</Menubar.Item>
<Menubar.Item class="menubar__item">Sync Fork</Menubar.Item>
<Menubar.Separator class="menubar__separator" />
<Menubar.Item class="menubar__item">
Open on GitHub
</Menubar.Item>
</Menubar.SubContent>
</Menubar.Portal>
</Menubar.Sub>
<Menubar.Separator class="menubar__separator" />
<Menubar.CheckboxItem
class="menubar__checkbox-item"
checked={showGitLog()}
onChange={setShowGitLog}
>
<Menubar.ItemIndicator class="menubar__item-indicator">
<CheckIcon />
</Menubar.ItemIndicator>
Show Git Log
</Menubar.CheckboxItem>
<Menubar.CheckboxItem
class="menubar__checkbox-item"
checked={showHistory()}
onChange={setShowHistory}
>
<Menubar.ItemIndicator class="menubar__item-indicator">
<CheckIcon />
</Menubar.ItemIndicator>
Show History
</Menubar.CheckboxItem>
<Menubar.Separator class="menubar__separator" />
<Menubar.Group>
<Menubar.GroupLabel class="menubar__group-label">
Branches
</Menubar.GroupLabel>
<Menubar.RadioGroup value={branch()} onChange={setBranch}>
<Menubar.RadioItem class="menubar__radio-item" value="main">
<Menubar.ItemIndicator class="menubar__item-indicator">
<DotFilledIcon />
</Menubar.ItemIndicator>
main
</Menubar.RadioItem>
<Menubar.RadioItem class="menubar__radio-item" value="develop">
<Menubar.ItemIndicator class="menubar__item-indicator">
<DotFilledIcon />
</Menubar.ItemIndicator>
develop
</Menubar.RadioItem>
</Menubar.RadioGroup>
</Menubar.Group>
</Menubar.Content>
</Menubar.Portal>
</Menubar.Menu>
<Menubar.Menu>
<Menubar.Trigger class="menubar__trigger">
File
</Menubar.Trigger>
<Menubar.Portal>
<Menubar.Content class="menubar__content">
<Menubar.Item class="menubar__item">
New Tab <div class="menubar__item-right-slot">⌘+T</div>
</Menubar.Item>
<Menubar.Item class="menubar__item">
New Window <div class="menubar__item-right-slot">⌘+N</div>
</Menubar.Item>
<Menubar.Item class="menubar__item" disabled>
New Incognito Window
</Menubar.Item>
<Menubar.Separator class="menubar__separator"/>
<Menubar.Sub overlap gutter={4} shift={-8}>
<Menubar.SubTrigger class="menubar__sub-trigger">
Share
<div class="menubar__item-right-slot">
<ChevronRightIcon width={20} height={20} />
</div>
</Menubar.SubTrigger>
<Menubar.Portal>
<Menubar.SubContent class="menubar__sub-content">
<Menubar.Item class="menubar__item">
Email Link
</Menubar.Item>
<Menubar.Item class="menubar__item">
Messages
</Menubar.Item>
<Menubar.Item class="menubar__item">
Notes
</Menubar.Item>
</Menubar.SubContent>
</Menubar.Portal>
</Menubar.Sub>
<Menubar.Separator class="menubar__separator" />
<Menubar.Item class="menubar__item">
Print... <div class="menubar__item-right-slot">⌘+P</div>
</Menubar.Item>
</Menubar.Content>
</Menubar.Portal>
</Menubar.Menu>
<Menubar.Menu>
<Menubar.Trigger class="menubar__trigger">
Edit
</Menubar.Trigger>
<Menubar.Portal>
<Menubar.Content class="menubar__content">
<Menubar.Item class="menubar__item">
Undo <div class="menubar__item-right-slot">⌘+Z</div>
</Menubar.Item>
<Menubar.Item class="menubar__item">
Redo <div class="menubar__item-right-slot">⇧+⌘+Z</div>
</Menubar.Item>
<Menubar.Separator class="menubar__separator"/>
<Menubar.Sub overlap gutter={4} shift={-8}>
<Menubar.SubTrigger class="menubar__sub-trigger">
Find
<div class="menubar__item-right-slot">
<ChevronRightIcon width={20} height={20} />
</div>
</Menubar.SubTrigger>
<Menubar.Portal>
<Menubar.SubContent class="menubar__sub-content">
<Menubar.Item class="menubar__item">
Search The Web
</Menubar.Item>
<Menubar.Separator class="menubar__separator"/>
<Menubar.Item class="menubar__item">
Find...
</Menubar.Item>
<Menubar.Item class="menubar__item">
Find Next
</Menubar.Item>
<Menubar.Item class="menubar__item">
Find Previous
</Menubar.Item>
</Menubar.SubContent>
</Menubar.Portal>
</Menubar.Sub>
<Menubar.Separator class="menubar__separator" />
<Menubar.Item class="menubar__item">
Cut
</Menubar.Item>
<Menubar.Item class="menubar__item">
Copy
</Menubar.Item>
<Menubar.Item class="menubar__item">
Paste
</Menubar.Item>
</Menubar.Content>
</Menubar.Portal>
</Menubar.Menu>
</Menubar>
);
}
```
```
Copytsx
import { Menubar } from "@kobalte/core/menubar";
import { createSignal } from "solid-js";
import { CheckIcon, ChevronRightIcon, DotFilledIcon } from "some-icon-library";
import "./style.css";
function App() {
const [showGitLog, setShowGitLog] = createSignal(true);
const [showHistory, setShowHistory] = createSignal(false);
const [branch, setBranch] = createSignal("main");
return (
<Menubar class="menubar__root">
<Menubar.Menu>
<Menubar.Trigger class="menubar__trigger">
Git
</Menubar.Trigger>
<Menubar.Portal>
<Menubar.Content class="menubar__content">
<Menubar.Item class="menubar__item">
Commit <div class="menubar__item-right-slot">⌘+K</div>
</Menubar.Item>
<Menubar.Item class="menubar__item">
Push <div class="menubar__item-right-slot">⇧+⌘+K</div>
</Menubar.Item>
<Menubar.Item class="menubar__item" disabled>
Update Project <div class="menubar__item-right-slot">⌘+T</div>
</Menubar.Item>
<Menubar.Sub overlap gutter={4} shift={-8}>
<Menubar.SubTrigger class="menubar__sub-trigger">
GitHub
<div class="menubar__item-right-slot">
<ChevronRightIcon width={20} height={20} />
</div>
</Menubar.SubTrigger>
<Menubar.Portal>
<Menubar.SubContent class="menubar__sub-content">
<Menubar.Item class="menubar__item">
Create Pull Request…
</Menubar.Item>
<Menubar.Item class="menubar__item">
View Pull Requests
</Menubar.Item>
<Menubar.Item class="menubar__item">Sync Fork</Menubar.Item>
<Menubar.Separator class="menubar__separator" />
<Menubar.Item class="menubar__item">
Open on GitHub
</Menubar.Item>
</Menubar.SubContent>
</Menubar.Portal>
</Menubar.Sub>
<Menubar.Separator class="menubar__separator" />
<Menubar.CheckboxItem
class="menubar__checkbox-item"
checked={showGitLog()}
onChange={setShowGitLog}
>
<Menubar.ItemIndicator class="menubar__item-indicator">
<CheckIcon />
</Menubar.ItemIndicator>
Show Git Log
</Menubar.CheckboxItem>
<Menubar.CheckboxItem
class="menubar__checkbox-item"
checked={showHistory()}
onChange={setShowHistory}
>
<Menubar.ItemIndicator class="menubar__item-indicator">
<CheckIcon />
</Menubar.ItemIndicator>
Show History
</Menubar.CheckboxItem>
<Menubar.Separator class="menubar__separator" />
<Menubar.Group>
<Menubar.GroupLabel class="menubar__group-label">
Branches
</Menubar.GroupLabel>
<Menubar.RadioGroup value={branch()} onChange={setBranch}>
<Menubar.RadioItem class="menubar__radio-item" value="main">
<Menubar.ItemIndicator class="menubar__item-indicator">
<DotFilledIcon />
</Menubar.ItemIndicator>
main
</Menubar.RadioItem>
<Menubar.RadioItem class="menubar__radio-item" value="develop">
<Menubar.ItemIndicator class="menubar__item-indicator">
<DotFilledIcon />
</Menubar.ItemIndicator>
develop
</Menubar.RadioItem>
</Menubar.RadioGroup>
</Menubar.Group>
</Menubar.Content>
</Menubar.Portal>
</Menubar.Menu>
<Menubar.Menu>
<Menubar.Trigger class="menubar__trigger">
File
</Menubar.Trigger>
<Menubar.Portal>
<Menubar.Content class="menubar__content">
<Menubar.Item class="menubar__item">
New Tab <div class="menubar__item-right-slot">⌘+T</div>
</Menubar.Item>
<Menubar.Item class="menubar__item">
New Window <div class="menubar__item-right-slot">⌘+N</div>
</Menubar.Item>
<Menubar.Item class="menubar__item" disabled>
New Incognito Window
</Menubar.Item>
<Menubar.Separator class="menubar__separator"/>
<Menubar.Sub overlap gutter={4} shift={-8}>
<Menubar.SubTrigger class="menubar__sub-trigger">
Share
<div class="menubar__item-right-slot">
<ChevronRightIcon width={20} height={20} />
</div>
</Menubar.SubTrigger>
<Menubar.Portal>
<Menubar.SubContent class="menubar__sub-content">
<Menubar.Item class="menubar__item">
Email Link
</Menubar.Item>
<Menubar.Item class="menubar__item">
Messages
</Menubar.Item>
<Menubar.Item class="menubar__item">
Notes
</Menubar.Item>
</Menubar.SubContent>
</Menubar.Portal>
</Menubar.Sub>
<Menubar.Separator class="menubar__separator" />
<Menubar.Item class="menubar__item">
Print... <div class="menubar__item-right-slot">⌘+P</div>
</Menubar.Item>
</Menubar.Content>
</Menubar.Portal>
</Menubar.Menu>
<Menubar.Menu>
<Menubar.Trigger class="menubar__trigger">
Edit
</Menubar.Trigger>
<Menubar.Portal>
<Menubar.Content class="menubar__content">
<Menubar.Item class="menubar__item">
Undo <div class="menubar__item-right-slot">⌘+Z</div>
</Menubar.Item>
<Menubar.Item class="menubar__item">
Redo <div class="menubar__item-right-slot">⇧+⌘+Z</div>
</Menubar.Item>
<Menubar.Separator class="menubar__separator"/>
<Menubar.Sub overlap gutter={4} shift={-8}>
<Menubar.SubTrigger class="menubar__sub-trigger">
Find
<div class="menubar__item-right-slot">
<ChevronRightIcon width={20} height={20} />
</div>
</Menubar.SubTrigger>
<Menubar.Portal>
<Menubar.SubContent class="menubar__sub-content">
<Menubar.Item class="menubar__item">
Search The Web
</Menubar.Item>
<Menubar.Separator class="menubar__separator"/>
<Menubar.Item class="menubar__item">
Find...
</Menubar.Item>
<Menubar.Item class="menubar__item">
Find Next
</Menubar.Item>
<Menubar.Item class="menubar__item">
Find Previous
</Menubar.Item>
</Menubar.SubContent>
</Menubar.Portal>
</Menubar.Sub>
<Menubar.Separator class="menubar__separator" />
<Menubar.Item class="menubar__item">
Cut
</Menubar.Item>
<Menubar.Item class="menubar__item">
Copy
</Menubar.Item>
<Menubar.Item class="menubar__item">
Paste
</Menubar.Item>
</Menubar.Content>
</Menubar.Portal>
</Menubar.Menu>
</Menubar>
);
}
```
## Usage
### Link Trigger
To make one of the menubar triggers a link instead of opening a menu, omit the `Menubar.Menu` and set `as="a"` on the `Menubar.Trigger`.
```
Copytsx
<Menubar>
<Menubar.Menu>
<Menubar.Trigger>Opens a regular menu</Menubar.Trigger>
<Menubar.Portal>
<Menubar.Content>...</Menubar.Content>
</Menubar.Portal>
</Menubar.Menu>
<Menubar.Trigger as="a" href="https://kobalte.dev">
Opens a link on click and enter
</Menubar.Trigger>
</Menubar>
```
```
Copytsx
<Menubar>
<Menubar.Menu>
<Menubar.Trigger>Opens a regular menu</Menubar.Trigger>
<Menubar.Portal>
<Menubar.Content>...</Menubar.Content>
</Menubar.Portal>
</Menubar.Menu>
<Menubar.Trigger as="a" href="https://kobalte.dev">
Opens a link on click and enter
</Menubar.Trigger>
</Menubar>
```
### Origin-aware animations
We expose a CSS custom property `--kb-menu-content-transform-origin` which can be used to animate the content from its computed origin.
```
Copycss
/* style.css */
.context-menu__content,
.context-menu__sub-content {
transform-origin: var(--kb-menu-content-transform-origin);
animation: contentHide 250ms ease-in forwards;
}
.context-menu__content[data-expanded],
.context-menu__sub-content[data-expanded] {
animation: contentShow 250ms ease-out;
}
@keyframes contentShow {
from {
opacity: 0;
transform: scale(0.96);
}
to {
opacity: 1;
transform: scale(1);
}
}
@keyframes contentHide {
from {
opacity: 1;
transform: scale(1);
}
to {
opacity: 0;
transform: scale(0.96);
}
}
```
```
Copycss
/* style.css */
.context-menu__content,
.context-menu__sub-content {
transform-origin: var(--kb-menu-content-transform-origin);
animation: contentHide 250ms ease-in forwards;
}
.context-menu__content[data-expanded],
.context-menu__sub-content[data-expanded] {
animation: contentShow 250ms ease-out;
}
@keyframes contentShow {
from {
opacity: 0;
transform: scale(0.96);
}
to {
opacity: 1;
transform: scale(1);
}
}
@keyframes contentHide {
from {
opacity: 1;
transform: scale(1);
}
to {
opacity: 0;
transform: scale(0.96);
}
}
```
## API Reference
### Menubar
`Menubar` is equivalent to the `Root` import from `@kobalte/core/menubar` (and deprecated `Menubar.Root`).
| Prop | Description |
| --- | --- |
| defaultValue | `string`<br> The value of the menu that should be open when initially rendered. Use when you do not need to control the value state. |
| value | `string`<br> The controlled value of the menu to open. Should be used in conjunction with onValueChange. |
| onValueChange | `(value: string | undefined | null) => void`<br> Event handler called when the value changes. |
| loop | `boolean`<br> When true, keyboard navigation will loop from last item to first, and vice versa. |
| focusOnAlt | `boolean`<br> When true, click on alt by itsef will focus this Menubar (some browsers interfere). |
### Menubar.Menu
| Prop | Description |
| --- | --- |
| onOpenChange | `(open: boolean) => void`<br> Event handler called when the open state of the menu changes. |
| id | `string`<br> A unique identifier for the component. The id is used to generate id attributes for nested components. If no id prop is provided, a generated id will be used. |
| modal | `boolean`<br> Whether the menu should be the only visible content for screen readers, when set to `true`: <br> \- interaction with outside elements will be disabled. <br> \- scroll will be locked. <br> \- focus will be locked inside the menu content. <br> \- elements outside the menu content will not be visible for screen readers. |
| preventScroll | `boolean`<br> Whether the scroll should be locked even if the menu is not modal. |
| forceMount | `boolean`<br> Used to force mounting the menu (portal and content) when more control is needed. Useful when controlling animation with SolidJS animation libraries. |
| value | `string`<br> A unique value that associates the item with an active value when the navigation menu is controlled. This prop is managed automatically when uncontrolled. |
`Menubar.Menu` also accepts the following props to customize the placement of the `Menubar.Content`.
| Prop | Description |
| --- | --- |
| placement | `Placement`<br> The placement of the menu content. |
| gutter | `number`<br> The distance between the menu content and the trigger element. By default, it's 0 plus half of the arrow offset, if it exists. |
| shift | `number`<br> The skidding of the menu content along the trigger element. |
| flip | `boolean | string`<br> Controls the behavior of the menu content when it overflows the viewport:<br> \- If a `boolean`, specifies whether the menu content should flip to the opposite side when it overflows.<br> \- If a `string`, indicates the preferred fallback placements when it overflows.<br>The placements must be spaced-delimited, e.g. "top left". |
| slide | `boolean`<br> Whether the menu content should slide when it overflows. |
| overlap | `boolean`<br> Whether the menu content can overlap the trigger element when it overflows. |
| sameWidth | `boolean`<br> Whether the menu content should have the same width as the trigger element. This will be exposed to CSS as `--kb-popper-anchor-width`. |
| fitViewport | `boolean`<br> Whether the menu content should fit the viewport. If this is set to true, the menu content will have `maxWidth` and `maxHeight` set to the viewport size. This will be exposed to CSS as `--kb-popper-available-width` and `--kb-popper-available-height`. |
| hideWhenDetached | `boolean`<br> Whether to hide the menu content when the trigger element becomes occluded. |
| detachedPadding | `number`<br> The minimum padding in order to consider the trigger element occluded. |
| arrowPadding | `number`<br> The minimum padding between the arrow and the menu content corner. |
| overflowPadding | `number`<br> The minimum padding between the menu content and the viewport edge. This will be exposed to CSS as `--kb-popper-overflow-padding`. |
### Menubar.Trigger
| Prop | Description |
| --- | --- |
| disabled | `boolean`<br> Whether the context menu trigger is disabled or not. |
| Data attribute | Description |
| --- | --- |
| data-expanded | Present when the menu is open. |
| data-closed | Present when the menu is close. |
| data-disabled | Present when the trigger is disabled. |
`Menubar.Icon`, `Menubar.Content`, `Menubar.SubTrigger` and `Menubar.SubContent` share the same `data-expanded` attribute.
### Menubar.Content
The popper positioner will copy the same `z-index` as the `Menubar.Content`.
| Prop | Description |
| --- | --- |
| onOpenAutoFocus | `(event: Event) => void`<br> Event handler called when focus moves into the component after opening. It can be prevented by calling `event.preventDefault`. |
| onCloseAutoFocus | `(event: Event) => void`<br> Event handler called when focus moves to the trigger after closing. It can be prevented by calling `event.preventDefault`. |
| onEscapeKeyDown | `(event: KeyboardEvent) => void`<br> Event handler called when the escape key is down. It can be prevented by calling `event.preventDefault`. |
| onPointerDownOutside | `(event: PointerDownOutsideEvent) => void`<br> Event handler called when a pointer event occurs outside the bounds of the component. It can be prevented by calling `event.preventDefault`. |
| onFocusOutside | `(event: FocusOutsideEvent) => void`<br> Event handler called when the focus moves outside the bounds of the component. It can be prevented by calling `event.preventDefault`. |
| onInteractOutside | `(event: InteractOutsideEvent) => void`<br> Event handler called when an interaction (pointer or focus event) happens outside the bounds of the component. It can be prevented by calling `event.preventDefault`. |
### Menubar.Arrow
| Prop | Description |
| --- | --- |
| size | `number`<br> The size of the arrow. |
### Menubar.Item
| Prop | Description |
| --- | --- |
| textValue | `string`<br> Optional text used for typeahead purposes. By default, the typeahead behavior will use the .textContent of the `Menubar.ItemLabel` part if provided, or fallback to the .textContent of the `Menubar.Item`. Use this when the content is complex, or you have non-textual content inside. |
| disabled | `boolean`<br> Whether the item is disabled or not. |
| closeOnSelect | `boolean`<br> Whether the menu should close when the item is activated. |
| onSelect | `() => void`<br> Event handler called when the user selects an item (via mouse or keyboard). |
| Data attribute | Description |
| --- | --- |
| data-disabled | Present when the item is disabled. |
| data-highlighted | Present when the item is highlighted. |
`Menubar.ItemLabel`, `Menubar.ItemDescription` and `Menubar.ItemIndicator` shares the same data-attributes.
### Menubar.ItemIndicator
| Prop | Description |
| --- | --- |
| forceMount | `boolean`<br> Used to force mounting when more control is needed. Useful when controlling animation with SolidJS animation libraries. |
### Menubar.RadioGroup
| Prop | Description |
| --- | --- |
| value | `string`<br> The controlled value of the menu radio item to check. |
| defaultValue | `string`<br> The value of the menu radio item that should be checked when initially rendered. Useful when you do not need to control the state of the radio group. |
| onChange | `(value: string) => void`<br> Event handler called when the value changes. |
| disabled | `boolean`<br> Whether the radio group is disabled or not. |
### Menubar.RadioItem
| Prop | Description |
| --- | --- |
| value | `string`<br> The value of the menu item radio. |
| textValue | `string`<br> Optional text used for typeahead purposes. By default, the typeahead behavior will use the .textContent of the `Menubar.ItemLabel` part if provided, or fallback to the .textContent of the `Menubar.Item`. Use this when the content is complex, or you have non-textual content inside. |
| disabled | `boolean`<br> Whether the item is disabled or not. |
| closeOnSelect | `boolean`<br> Whether the menu should close when the item is checked. |
| onSelect | `() => void`<br> Event handler called when the user selects an item (via mouse or keyboard). |
| Data attribute | Description |
| --- | --- |
| data-disabled | Present when the item is disabled. |
| data-checked | Present when the item is checked. |
| data-highlighted | Present when the item is highlighted. |
### Menubar.CheckboxItem
| Prop | Description |
| --- | --- |
| checked | `boolean`<br> The controlled checked state of the item. |
| defaultChecked | `boolean`<br> The default checked state when initially rendered. Useful when you do not need to control the checked state. |
| onChange | `(checked: boolean) => void`<br> Event handler called when the checked state of the item changes. |
| textValue | `string`<br> Optional text used for typeahead purposes. By default, the typeahead behavior will use the .textContent of the `Menubar.ItemLabel` part if provided, or fallback to the .textContent of the `Menubar.Item`. Use this when the content is complex, or you have non-textual content inside. |
| indeterminate | `boolean`<br> Whether the item is in an indeterminate state. |
| disabled | `boolean`<br> Whether the item is disabled or not. |
| closeOnSelect | `boolean`<br> Whether the menu should close when the item is checked/unchecked. |
| onSelect | `() => void`<br> Event handler called when the user selects an item (via mouse or keyboard). |
| Data attribute | Description |
| --- | --- |
| data-disabled | Present when the item is disabled. |
| data-indeterminate | Present when the item is in an indeterminate state. |
| data-checked | Present when the item is checked. |
| data-highlighted | Present when the item is highlighted. |
### Menubar.Sub
| Prop | Description |
| --- | --- |
| open | `boolean`<br> The controlled open state of the sub menu. |
| defaultOpen | `boolean`<br> The default open state when initially rendered. Useful when you do not need to control the open state. |
| onOpenChange | `(open: boolean) => void`<br> Event handler called when the open state of the sub menu changes. |
`Menubar.Sub` also accepts the following props to customize the placement of the `Menubar.SubContent`.
| Prop | Description |
| --- | --- |
| getAnchorRect | `(anchor?: HTMLElement) => AnchorRect | undefined`<br> Function that returns the trigger element's DOMRect. |
| gutter | `number`<br> The distance between the sub menu content and the trigger element. By default, it's 0 plus half of the arrow offset, if it exists. |
| shift | `number`<br> The skidding of the sub menu content along the trigger element. |
| slide | `boolean`<br> Whether the sub menu content should slide when it overflows. |
| overlap | `boolean`<br> Whether the sub menu content can overlap the trigger element when it overflows. |
| fitViewport | `boolean`<br> Whether the sub menu content should fit the viewport. If this is set to true, the sub menu content will have `maxWidth` and `maxHeight` set to the viewport size. This will be exposed to CSS as `--kb-popper-available-width` and `--kb-popper-available-height`. |
| hideWhenDetached | `boolean`<br> Whether to hide the sub menu content when the trigger element becomes occluded. |
| detachedPadding | `number`<br> The minimum padding in order to consider the trigger element occluded. |
| arrowPadding | `number`<br> The minimum padding between the arrow and the sub menu content corner. |
| overflowPadding | `number`<br> The minimum padding between the sub menu content and the viewport edge. This will be exposed to CSS as `--kb-popper-overflow-padding`. |
### Menubar.SubTrigger
| Prop | Description |
| --- | --- |
| textValue | `string`<br> Optional text used for typeahead purposes. By default, the typeahead behavior will use the .textContent of the `Menubar.SubTrigger`. Use this when the content is complex, or you have non-textual content inside. |
| disabled | `boolean`<br> Whether the sub menu trigger is disabled or not. |
| Data attribute | Description |
| --- | --- |
| data-disabled | Present when the item is disabled. |
| data-highlighted | Present when the item is highlighted. |
### Menubar.SubContent
| Prop | Description |
| --- | --- |
| onEscapeKeyDown | `(event: KeyboardEvent) => void`<br> Event handler called when the escape key is down. It can be prevented by calling `event.preventDefault`. |
| onPointerDownOutside | `(event: PointerDownOutsideEvent) => void`<br> Event handler called when a pointer event occurs outside the bounds of the component. It can be prevented by calling `event.preventDefault`. |
| onFocusOutside | `(event: FocusOutsideEvent) => void`<br> Event handler called when the focus moves outside the bounds of the component. It can be prevented by calling `event.preventDefault`. |
| onInteractOutside | `(event: InteractOutsideEvent) => void`<br> Event handler called when an interaction (pointer or focus event) happens outside the bounds of the component. It can be prevented by calling `event.preventDefault`. |
## Rendered elements
| Component | Default rendered element |
| --- | --- |
| `Menubar` | `div` |
| `Menubar.Menu` | none |
| `Menubar.Trigger` | `div` |
| `Menubar.Icon` | `div` |
| `Menubar.Portal` | `Portal` |
| `Menubar.Content` | `div` |
| `Menubar.Arrow` | `div` |
| `Menubar.Separator` | `hr` |
| `Menubar.Group` | `div` |
| `Menubar.GroupLabel` | `span` |
| `Menubar.Sub` | none |
| `Menubar.SubTrigger` | `div` |
| `Menubar.SubContent` | `div` |
| `Menubar.Item` | `div` |
| `Menubar.ItemLabel` | `div` |
| `Menubar.ItemDescription` | `div` |
| `Menubar.ItemIndicator` | `div` |
| `Menubar.RadioGroup` | `div` |
| `Menubar.RadioItem` | `div` |
| `Menubar.CheckboxItem` | `div` |
## Accessibility
### Keyboard Interactions
| Key | Description |
| --- | --- |
| `Space` | When focus is on an item, activates the item. |
| `Enter` | When focus is on an item, activates the item. |
| `ArrowDown` | When focus is on an item, moves focus to the next item. |
| `ArrowUp` | When focus is on an item, moves focus to the previous item. |
| `ArrowRight` | When focus is on an item (not sub menu trigger), moves focus to the next menu. |
| `ArrowLeft` | When focus is on an item (not sub menu item), moves focus to the previous menu. |
| `ArrowRight` / `ArrowLeft` | When focus is on a sub menu trigger, opens or closes the submenu depending on reading direction. |
| `Home` | When focus is on an item, moves focus to first item. |
| `End` | When focus is on an item, moves focus to last item. |
| `Esc` | Closes the menu. |
Previous[←Link](https://kobalte.dev/docs/core/components/link)Next[Meter→](https://kobalte.dev/docs/core/components/meter)

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,93 @@
{
"name": "@kobalte/core",
"version": "0.13.11",
"private": false,
"description": "Unstyled components and primitives for building accessible web apps and design systems with SolidJS.",
"keywords": \[\
"solid",\
"solidjs",\
"ui",\
"library",\
"design-system",\
"components",\
"headless",\
"unstyled",\
"aria"\
\],
"homepage": "https://github.com/kobaltedev/kobalte/tree/main/packages/core#readme",
"bugs": {
"url": "https://github.com/kobaltedev/kobalte/issues"
},
"repository": {
"type": "git",
"url": "git+https://github.com/kobaltedev/kobalte.git"
},
"license": "MIT",
"author": "jer3m01 ",
"contributors": \[\
"Fabien Marie-Louise "\
\],
"sideEffects": false,
"type": "module",
"exports": {
".": {
"types": "./dist/index.d.ts",
"solid": "./dist/index.jsx",
"default": "./dist/index.js"
},
"./\*": {
"types": "./dist/\*/index.d.ts",
"solid": "./dist/\*/index.jsx",
"default": "./dist/\*/index.js"
},
"./src/\*": "./src/\*"
},
"main": "dist/index.js",
"module": "dist/index.js",
"types": "dist/index.d.ts",
"typesVersions": {
"\*": {
"\*": \[\
"./dist/\*/index.d.ts",\
"./dist/index.d.ts"\
\]
}
},
"source": "src/index.tsx",
"files": \[\
"dist",\
"src",\
"NOTICE.txt"\
\],
"scripts": {
"build": "pnpm build:cp && pnpm build:tsup",
"build:cp": "cp ../../NOTICE.txt .",
"build:tsup": "NODE\_OPTIONS=\\"--max-old-space-size=8192\\" tsup",
"clean": "rm -rf .turbo && rm -rf node\_modules && rm -rf dist && rm NOTICE.txt",
"dev": "vite serve dev --host",
"test-old": "jest --passWithNoTests",
"test": "vitest run",
"typecheck": "tsc --noEmit"
},
"dependencies": {
"@floating-ui/dom": "^1.5.1",
"@internationalized/date": "^3.4.0",
"@internationalized/number": "^3.2.1",
"@kobalte/utils": "^0.9.1",
"@solid-primitives/props": "^3.1.8",
"@solid-primitives/resize-observer": "^2.0.26",
"solid-presence": "^0.1.8",
"solid-prevent-scroll": "^0.1.4"
},
"devDependencies": {
"@kobalte/tests": "workspace:\*",
"esbuild-plugin-solid": "^0.5.0",
"tsup": "7.2.0"
},
"peerDependencies": {
"solid-js": "^1.8.15"
},
"publishConfig": {
"access": "public"
}
}

View File

@ -1,355 +0,0 @@
# Polymorphism
All component parts that render a DOM element have an `as` prop.
## The `as` prop
For simple use cases the `as` prop can be used, either with native HTML elements or custom Solid components:
```
Copytsx
import { Tabs } from "@kobalte/core/tabs";
import { MyCustomButton } from "./components";
function App() {
return (
<Tabs>
<Tabs.List>
{/* Render an anchor tag instead of the default button */}
<Tabs.Trigger value="one" as="a">
A Trigger
</Tabs.Trigger>
{/* Render MyCustomButton instead of the default button */}
<Tabs.Trigger value="one" as={MyCustomButton}>
Custom Button Trigger
</Tabs.Trigger>
</Tabs.List>
<Tabs.Content value="one">Content one</Tabs.Content>
</Tabs>
);
}
```
```
Copytsx
import { Tabs } from "@kobalte/core/tabs";
import { MyCustomButton } from "./components";
function App() {
return (
<Tabs>
<Tabs.List>
{/* Render an anchor tag instead of the default button */}
<Tabs.Trigger value="one" as="a">
A Trigger
</Tabs.Trigger>
{/* Render MyCustomButton instead of the default button */}
<Tabs.Trigger value="one" as={MyCustomButton}>
Custom Button Trigger
</Tabs.Trigger>
</Tabs.List>
<Tabs.Content value="one">Content one</Tabs.Content>
</Tabs>
);
}
```
## The `as` prop callback
For more advanced use cases the `as` prop can accept a callback.
The main reason to use a callback over the normal `as` prop is being able to set props without interfering with Kobalte.
When using this pattern the following rules apply to the callback:
- You must spread the props forwarded to your callback onto your node/component.
- Custom props are passed as is from the parent.
- Kobalte options are not passed to the callback, only the resulting html attributes.
- You should set your event handlers on the parent and not inside your callback.
```
Copytsx
import { Tabs } from "@kobalte/core/tabs";
import { MyCustomButton } from "./components";
function App() {
return (
<Tabs>
<Tabs.List>
{/* The `value` prop is used by Kobalte and not passed to MyCustomButton */}
<Tabs.Trigger value="one" as={MyCustomButton}>
A Trigger
</Tabs.Trigger>
{/* The `value` prop is used by Kobalte and not passed to MyCustomButton */}
<Tabs.Trigger
value="one"
as={props => (
// The `value` prop is directly passed to MyCustomButton
<MyCustomButton value="custom" {...props} />
)}
>
Custom Button Trigger
</Tabs.Trigger>
</Tabs.List>
<Tabs.Content value="one">Content one</Tabs.Content>
</Tabs>
);
}
```
```
Copytsx
import { Tabs } from "@kobalte/core/tabs";
import { MyCustomButton } from "./components";
function App() {
return (
<Tabs>
<Tabs.List>
{/* The `value` prop is used by Kobalte and not passed to MyCustomButton */}
<Tabs.Trigger value="one" as={MyCustomButton}>
A Trigger
</Tabs.Trigger>
{/* The `value` prop is used by Kobalte and not passed to MyCustomButton */}
<Tabs.Trigger
value="one"
as={props => (
// The `value` prop is directly passed to MyCustomButton
<MyCustomButton value="custom" {...props} />
)}
>
Custom Button Trigger
</Tabs.Trigger>
</Tabs.List>
<Tabs.Content value="one">Content one</Tabs.Content>
</Tabs>
);
}
```
You can optionally use a type helper to get the exact types passed to your callback:
```
Copytsx
import { Tabs, TabsTriggerOptions, TabsTriggerRenderProps } from "@kobalte/core/tabs";
import { PolymorphicCallbackProps } from "@kobalte/core/polymorphic";
<Tabs.Trigger
value="one"
as={(
props: PolymorphicCallbackProps<
MyCustomButtonProps,
TabsTriggerOptions,
TabsTriggerRenderProps
>,
) => (
// The `value` prop is directly passed to MyCustomButton
<MyCustomButton value="custom" {...props} />
)}
>
Custom Button Trigger
</Tabs.Trigger>;
```
```
Copytsx
import { Tabs, TabsTriggerOptions, TabsTriggerRenderProps } from "@kobalte/core/tabs";
import { PolymorphicCallbackProps } from "@kobalte/core/polymorphic";
<Tabs.Trigger
value="one"
as={(
props: PolymorphicCallbackProps<
MyCustomButtonProps,
TabsTriggerOptions,
TabsTriggerRenderProps
>,
) => (
// The `value` prop is directly passed to MyCustomButton
<MyCustomButton value="custom" {...props} />
)}
>
Custom Button Trigger
</Tabs.Trigger>;
```
## Event lifecycle
Setting custom event handlers on component will call your custom handler before Kobalte's.
## Types
This section is mainly for library author that want to build on top of Kobalte and expose the correct types
to your end users.
Every component that renders an HTML element has the following types:
- `ComponentOptions`
- `ComponentCommonProps<T>`
- `ComponentRenderProps`
- `ComponentProps<T>`
For example, `Tabs.Trigger` has the types `TabsTriggerOptions`, `TabsTriggerCommonProps<T>`,
`TabsTriggerRenderProps` and `TabsTriggerProps<T>`.
Components themselves accept props as `PolymorphicProps<T, ComponentProps>` where `T` is a generic
that extends `ValidComponent` and `ComponentProps` are the props of the Kobalte component.
This type allows components to accept Kobalte's props and all other props accepted by `T`.
### `ComponentOptions`
This type contains all custom props consumed by Kobalte, these props do not exist in HTML.
These are not passed to the HTML element nor to the `as` callback.
### `ComponentCommonProps<T>`
This type contains HTML attributes optionally accepted by the Kobalte component and will
be forwarded to the rendered DOM node. These are managed by Kobalte but can be customized by the end
user. It includes attributes such as `id`, `ref`, event handlers, etc. The generic is used by `ref` and event handlers,
by default it is `HTMLElement`.
### `ComponentRenderProps`
This type extends `ComponentCommonProps` and additionally contains attributes that are passed
to the DOM node and fully managed by Kobalte. You should never assign these yourself or set them on
the Kobalte component. Modifying these props will break your component's behavior and accessibility.
### `ComponentProps<T>`
This is the final type exported by components, it is equal to `ComponentOptions & Partial<ComponentCommonProps>`.
It combines all props expected by Kobalte's component. The generic is used by the CommonProps, by default it is `HTMLElement`.
### `PolymorphicProps<T, ComponentProps>`
If you're writing a custom component and want to expose Kobalte's `as` prop to the end user
and keep proper typing, be sure to use `PolymorphicProps<T, ComponentProps>` for your props type.
```
Copytsx
import { Tabs, TabsTriggerProps } from "@kobalte/core/tabs";
import { PolymorphicProps } from "@kobalte/core/polymorphic";
// Optionally extend `TabsTriggerProps` if you wish to
// expose Kobalte props to your end user.
interface CustomProps<T extends ValidComponent = "button"> extends TabsTriggerProps<T> {
variant: "default" | "outline";
}
// Your generic `T` should extend ValidComponent and have a default value of the default DOM node.
function CustomTabsTrigger<T extends ValidComponent = "button">(
props: PolymorphicProps<T, CustomProps<T>>,
) {
// Typescript degrades typechecking when using generics, as long as we
// spread `others` to our element, we can effectively ignore them.
const [local, others] = splitProps(props as CustomProps, ["variant"]);
return (
<Tabs.Trigger
// Optional, will default to Kobalte otherwise.
// This should match with your generic `T` default.
as="button"
class={local.variant === "default" ? "default-trigger" : "outline-trigger"}
// Make sure to spread these props!
{...others}
/>
);
}
```
```
Copytsx
import { Tabs, TabsTriggerProps } from "@kobalte/core/tabs";
import { PolymorphicProps } from "@kobalte/core/polymorphic";
// Optionally extend `TabsTriggerProps` if you wish to
// expose Kobalte props to your end user.
interface CustomProps<T extends ValidComponent = "button"> extends TabsTriggerProps<T> {
variant: "default" | "outline";
}
// Your generic `T` should extend ValidComponent and have a default value of the default DOM node.
function CustomTabsTrigger<T extends ValidComponent = "button">(
props: PolymorphicProps<T, CustomProps<T>>,
) {
// Typescript degrades typechecking when using generics, as long as we
// spread `others` to our element, we can effectively ignore them.
const [local, others] = splitProps(props as CustomProps, ["variant"]);
return (
<Tabs.Trigger
// Optional, will default to Kobalte otherwise.
// This should match with your generic `T` default.
as="button"
class={local.variant === "default" ? "default-trigger" : "outline-trigger"}
// Make sure to spread these props!
{...others}
/>
);
}
```
If you do not wish to allow changing the element type, you can simplify your types by making
props: `OverrideComponentProps<"button", CustomProps>`, replace `"button"` with the correct
tagname for other components, imported from `"@kobalte/utils"`.
If you also want to export exact types, you can re-export and extends component types:
```
Copytsx
export interface CustomTabsTriggerOptions extends TabsTriggerOptions {
variant: "default" | "outline";
}
export interface CustomTabsTriggerCommonProps<T extends HTMLElement = HTMLElement> extends TabsTriggerCommonProps<T> {
// If you allow users to set classes and extend them.
//class: string;
}
export interface CustomTabsTriggerRenderProps
extends CustomTabsTriggerCommonProps,
TabsTriggerRenderProps {
// If you do not allow users to set classes and manage all of them.
class: string;
}
export type CustomTabsTriggerProps<T extends ValidComponent = "button"> = CustomTabsTriggerOptions &
Partial<CustomTabsTriggerCommonProps<ElementOf<T>>>;
export function CustomTabsTrigger<T extends ValidComponent = "button">(
props: PolymorphicProps<T, CustomTabsTriggerProps<T>,
) {}
```
```
Copytsx
export interface CustomTabsTriggerOptions extends TabsTriggerOptions {
variant: "default" | "outline";
}
export interface CustomTabsTriggerCommonProps<T extends HTMLElement = HTMLElement> extends TabsTriggerCommonProps<T> {
// If you allow users to set classes and extend them.
//class: string;
}
export interface CustomTabsTriggerRenderProps
extends CustomTabsTriggerCommonProps,
TabsTriggerRenderProps {
// If you do not allow users to set classes and manage all of them.
class: string;
}
export type CustomTabsTriggerProps<T extends ValidComponent = "button"> = CustomTabsTriggerOptions &
Partial<CustomTabsTriggerCommonProps<ElementOf<T>>>;
export function CustomTabsTrigger<T extends ValidComponent = "button">(
props: PolymorphicProps<T, CustomTabsTriggerProps<T>,
) {}
```
`ElementOf<T>` is a helper from `"@kobalte/core/polymorphic"` that converts a tag name into its element
(e.g. `ElementOf<"button"> = HTMLButtonElement`).
Previous[←Animation](https://kobalte.dev/docs/core/overview/animation)Next[Server side rendering→](https://kobalte.dev/docs/core/overview/ssr)

View File

@ -0,0 +1,406 @@
# Popover
A popover positioned relative to an anchor element.
## Import
```
Copyts
import { Popover } from "@kobalte/core/popover";
// or
import { Root, Trigger, ... } from "@kobalte/core/popover";
// or (deprecated)
import { Popover } from "@kobalte/core";
```
```
Copyts
import { Popover } from "@kobalte/core/popover";
// or
import { Root, Trigger, ... } from "@kobalte/core/popover";
// or (deprecated)
import { Popover } from "@kobalte/core";
```
## Features
- Follow the [WAI ARIA Popover](https://www.w3.org/WAI/ARIA/apg/patterns/popovermodal/) design pattern.
- Supports modal and non-modal modes.
- Provides screen reader announcements via rendered title and description.
- Focus is fully managed and customizable.
- Optionally render a pointing arrow.
- Can be controlled or uncontrolled.
## Anatomy
The popover consists of:
- **Popover:** The root container for a popover.
- **Popover.Trigger:** The button that opens the popover.
- **Popover.Anchor:** An optional element to position the `Popover.Content` against.
- **Popover.Portal:** Portals its children into the `body` when the popover is open.
- **Popover.Content:** Contains the content to be rendered when the popover is open.
- **Popover.Arrow:** An optional arrow element to render alongside the popover.
- **Popover.CloseButton:** The button that closes the popover.
- **Popover.Title:** An accessible title to be announced when the popover is open.
- **Popover.Description:** An optional accessible description to be announced when the popover is open.
```
Copytsx
<Popover>
<Popover.Trigger />
<Popover.Anchor />
<Popover.Portal>
<Popover.Content>
<Popover.Arrow />
<Popover.CloseButton />
<Popover.Title />
<Popover.Description />
</Popover.Content>
</Popover.Portal>
</Popover>
```
```
Copytsx
<Popover>
<Popover.Trigger />
<Popover.Anchor />
<Popover.Portal>
<Popover.Content>
<Popover.Arrow />
<Popover.CloseButton />
<Popover.Title />
<Popover.Description />
</Popover.Content>
</Popover.Portal>
</Popover>
```
## Example
Open
index.tsxstyle.css
```
Copytsx
import { Popover } from "@kobalte/core/popover";
import { CrossIcon } from "some-icon-library";
import "./style.css";
function App() {
return (
<Popover>
<Popover.Trigger class="popover__trigger">Open</Popover.Trigger>
<Popover.Portal>
<Popover.Content class="popover__content">
<Popover.Arrow />
<div class="popover__header">
<Popover.Title class="popover__title">About Kobalte</Popover.Title>
<Popover.CloseButton class="popover__close-button">
<CrossIcon />
</Popover.CloseButton>
</div>
<Popover.Description class="popover__description">
A UI toolkit for building accessible web apps and design systems with SolidJS.
</Popover.Description>
</Popover.Content>
</Popover.Portal>
</Popover>
);
}
```
```
Copytsx
import { Popover } from "@kobalte/core/popover";
import { CrossIcon } from "some-icon-library";
import "./style.css";
function App() {
return (
<Popover>
<Popover.Trigger class="popover__trigger">Open</Popover.Trigger>
<Popover.Portal>
<Popover.Content class="popover__content">
<Popover.Arrow />
<div class="popover__header">
<Popover.Title class="popover__title">About Kobalte</Popover.Title>
<Popover.CloseButton class="popover__close-button">
<CrossIcon />
</Popover.CloseButton>
</div>
<Popover.Description class="popover__description">
A UI toolkit for building accessible web apps and design systems with SolidJS.
</Popover.Description>
</Popover.Content>
</Popover.Portal>
</Popover>
);
}
```
## Usage
### Default open
An initial, uncontrolled open value can be provided using the `defaultOpen` prop.
```
Copytsx
<Popover defaultOpen>...</Popover>
```
```
Copytsx
<Popover defaultOpen>...</Popover>
```
### Controlled open
The `open` prop can be used to make the open state controlled. The `onOpenChange` event is fired when the user presses the trigger, close button or interact outside, and receives the new value.
Open
```
Copytsx
import { createSignal } from "solid-js";
function ControlledExample() {
const [open, setOpen] = createSignal(false);
return (
<Popover open={open()} onOpenChange={setOpen}>
<Popover.Trigger>{open() ? "Close" : "Open"}</Popover.Trigger>
<Popover.Portal>
<Popover.Content>...</Popover.Content>
</Popover.Portal>
</Popover>
);
}
```
```
Copytsx
import { createSignal } from "solid-js";
function ControlledExample() {
const [open, setOpen] = createSignal(false);
return (
<Popover open={open()} onOpenChange={setOpen}>
<Popover.Trigger>{open() ? "Close" : "Open"}</Popover.Trigger>
<Popover.Portal>
<Popover.Content>...</Popover.Content>
</Popover.Portal>
</Popover>
);
}
```
### With custom anchor
Use the `Popover.Anchor` component to anchor the content to another element if you do not want to use the trigger as the anchor.
The popover opens when you click here.
But it's anchored to the whole div.
```
Copytsx
<Popover>
<Popover.Anchor>
<p>
The popover opens when you click <Popover.Trigger>here</Popover.Trigger>.
</p>
<p>But it's anchored to the whole div.</p>
</Popover.Anchor>
<Popover.Portal>
<Popover.Content>...</Popover.Content>
</Popover.Portal>
</Popover>
```
```
Copytsx
<Popover>
<Popover.Anchor>
<p>
The popover opens when you click <Popover.Trigger>here</Popover.Trigger>.
</p>
<p>But it's anchored to the whole div.</p>
</Popover.Anchor>
<Popover.Portal>
<Popover.Content>...</Popover.Content>
</Popover.Portal>
</Popover>
```
### Origin-aware animations
We expose a CSS custom property `--kb-popover-content-transform-origin` which can be used to animate the content from its computed origin.
```
Copycss
/* style.css */
.popover__content {
transform-origin: var(--kb-popover-content-transform-origin);
animation: contentHide 250ms ease-in forwards;
}
.popover__content[data-expanded] {
animation: contentShow 250ms ease-out;
}
@keyframes contentShow {
from {
opacity: 0;
transform: scale(0.96);
}
to {
opacity: 1;
transform: scale(1);
}
}
@keyframes contentHide {
from {
opacity: 1;
transform: scale(1);
}
to {
opacity: 0;
transform: scale(0.96);
}
}
```
```
Copycss
/* style.css */
.popover__content {
transform-origin: var(--kb-popover-content-transform-origin);
animation: contentHide 250ms ease-in forwards;
}
.popover__content[data-expanded] {
animation: contentShow 250ms ease-out;
}
@keyframes contentShow {
from {
opacity: 0;
transform: scale(0.96);
}
to {
opacity: 1;
transform: scale(1);
}
}
@keyframes contentHide {
from {
opacity: 1;
transform: scale(1);
}
to {
opacity: 0;
transform: scale(0.96);
}
}
```
## API Reference
### Popover
`Popover` is equivalent to the `Root` import from `@kobalte/core/popover` (and deprecated `Popover.Root`).
| Prop | Description |
| --- | --- |
| open | `boolean`<br> The controlled open state of the popover. |
| defaultOpen | `boolean`<br> The default open state when initially rendered. Useful when you do not need to control the open state. |
| onOpenChange | `(open: boolean) => void`<br> Event handler called when the open state of the popover changes. |
| id | `string`<br> A unique identifier for the component. The id is used to generate id attributes for nested components. If no id prop is provided, a generated id will be used. |
| modal | `boolean`<br> Whether the popover should be the only visible content for screen readers, when set to `true`: <br> \- interaction with outside elements will be disabled. <br> \- scroll will be locked. <br> \- focus will be locked inside the popover content. <br> \- elements outside the popover content will not be visible for screen readers. |
| preventScroll | `boolean`<br> Whether the scroll should be locked even if the popover is not modal. |
| forceMount | `boolean`<br> Used to force mounting the popover (portal and content) when more control is needed. Useful when controlling animation with SolidJS animation libraries. |
| translations | [`PopoverIntlTranslations`](https://github.com/kobaltedev/kobalte/blob/main/packages/core/src/popover/popover.intl.ts)<br> Localization strings. |
`Popover` also accepts the following props to customize the placement of the `Popover.Content`.
| Prop | Description |
| --- | --- |
| getAnchorRect | `(anchor?: HTMLElement) => AnchorRect | undefined`<br> Function that returns the anchor element's DOMRect. |
| anchorRef | `Accessor<HTMLElement | undefined>`<br> A ref for the anchor element. Useful if you want to use an element outside `Popover` as the popover anchor. |
| placement | `Placement`<br> The placement of the popover. |
| gutter | `number`<br> The distance between the popover and the trigger/anchor element. By default, it's 0 plus half of the arrow offset, if it exists. |
| shift | `number`<br> The skidding of the popover along the anchor element. |
| flip | `boolean | string`<br> Controls the behavior of the popover when it overflows the viewport:<br> \- If a `boolean`, specifies whether the popover should flip to the opposite side when it overflows.<br> \- If a `string`, indicates the preferred fallback placements when it overflows.<br>The placements must be spaced-delimited, e.g. "top left". |
| slide | `boolean`<br> Whether the popover should slide when it overflows. |
| overlap | `boolean`<br> Whether the popover can overlap the anchor element when it overflows. |
| sameWidth | `boolean`<br> Whether the popover should have the same width as the anchor element. This will be exposed to CSS as `--kb-popper-anchor-width`. |
| fitViewport | `boolean`<br> Whether the popover should fit the viewport. If this is set to true, the popover content will have `maxWidth` and `maxHeight` set to the viewport size. This will be exposed to CSS as `--kb-popper-available-width` and `--kb-popper-available-height`. |
| hideWhenDetached | `boolean`<br> Whether to hide the popover when the anchor element becomes occluded. |
| detachedPadding | `number`<br> The minimum padding in order to consider the anchor element occluded. |
| arrowPadding | `number`<br> The minimum padding between the arrow and the popover corner. |
| overflowPadding | `number`<br> The minimum padding between the popover and the viewport edge. This will be exposed to CSS as `--kb-popper-overflow-padding`. |
### Popover.Trigger
`Popover.Trigger` consists of [Button](https://kobalte.dev/docs/core/components/button).
| Data attribute | Description |
| --- | --- |
| data-expanded | Present when the popover is open. |
| data-closed | Present when the popover is close. |
`Popover.Anchor` , `Popover.Content` , `Popover.Arrow`, `Popover.Title`, `Popover.Description` and `Popover.CloseButton` share the same data-attributes.
### Popover.Content
The popper positioner will copy the same `z-index` as the `Popover.Content`.
| Prop | Description |
| --- | --- |
| onOpenAutoFocus | `(event: Event) => void`<br> Event handler called when focus moves into the component after opening. It can be prevented by calling `event.preventDefault`. |
| onCloseAutoFocus | `(event: Event) => void`<br> Event handler called when focus moves to the trigger after closing. It can be prevented by calling `event.preventDefault`. |
| onEscapeKeyDown | `(event: KeyboardEvent) => void`<br> Event handler called when the escape key is down. It can be prevented by calling `event.preventDefault`. |
| onPointerDownOutside | `(event: PointerDownOutsideEvent) => void`<br> Event handler called when a pointer event occurs outside the bounds of the component. It can be prevented by calling `event.preventDefault`. |
| onFocusOutside | `(event: FocusOutsideEvent) => void`<br> Event handler called when the focus moves outside the bounds of the component. It can be prevented by calling `event.preventDefault`. |
| onInteractOutside | `(event: InteractOutsideEvent) => void`<br> Event handler called when an interaction (pointer or focus event) happens outside the bounds of the component. It can be prevented by calling `event.preventDefault`. |
### Popover.Arrow
| Prop | Description |
| --- | --- |
| size | `number`<br> The size of the arrow. |
## Rendered elements
| Component | Default rendered element |
| --- | --- |
| `Popover` | none |
| `Popover.Trigger` | `button` |
| `Popover.Anchor` | `div` |
| `Popover.Portal` | `Portal` |
| `Popover.Content` | `div` |
| `Popover.Arrow` | `div` |
| `Popover.CloseButton` | `button` |
| `Popover.Title` | `h2` |
| `Popover.Description` | `p` |
## Accessibility
### Keyboard Interactions
| Key | Description |
| --- | --- |
| `Space` | When focus is on the trigger, opens/closes the popover. |
| `Enter` | When focus is on the trigger, opens/closes the popover. |
| `Tab` | Moves focus to the next focusable element. |
| `Shift` \+ `Tab` | Moves focus to the previous focusable element. |
| `Esc` | Closes the popover and moves focus to the trigger. |
Previous[←Pagination](https://kobalte.dev/docs/core/components/pagination)Next[Progress→](https://kobalte.dev/docs/core/components/progress)

View File

@ -0,0 +1,618 @@
# Rating Group
Allows users to rate items using a set of icons.
## Import
```
Copyts
import { RatingGroup } from "@kobalte/core/rating-group";
// or
import { Root, Label, ... } from "@kobalte/core/rating-group";
```
```
Copyts
import { RatingGroup } from "@kobalte/core/rating-group";
// or
import { Root, Label, ... } from "@kobalte/core/rating-group";
```
## Features
- Precise ratings with half-value increments.
- Syncs with form reset events.
- Group and rating labeling support for assistive technology.
- Can be controlled or uncontrolled.
## Anatomy
The rating group consists of:
- **RatingGroup**: The root container for the rating group.
- **RatingGroup.Control**: The container for the rating items.
- **RatingGroup.Label**: The label that gives the user information on the rating group.
- **RatingGroup.HiddenInput**: The native html input that is visually hidden in the rating group.
- **RatingGroup.Description**: The description that gives the user more information on the rating group.
- **RatingGroup.ErrorMessage**: The error message that gives the user information about how to fix a validation error on the rating group.
The rating item consists of:
- **RatingGroup.Item**: The root container for a rating item.
- **RatingGroup.ItemControl**: The element that visually represents a rating item.
- **RatingGroup.ItemLabel**: The label that gives the user information on the rating item.
- **RatingGroup.ItemDescription**: The description that gives the user more information on the rating item.
```
Copytsx
<RatingGroup>
<RatingGroup.Label />
<RatingGroup.Control>
<RatingGroup.Item>
<RatingGroup.ItemControl />
<RatingGroup.ItemLabel />
<RatingGroup.ItemDescription />
</RatingGroup.Item>
<RatingGroup.Control>
<RatingGroup.HiddenInput />
<RatingGroup.Description />
<RatingGroup.ErrorMessage />
</RatingGroup>
```
```
Copytsx
<RatingGroup>
<RatingGroup.Label />
<RatingGroup.Control>
<RatingGroup.Item>
<RatingGroup.ItemControl />
<RatingGroup.ItemLabel />
<RatingGroup.ItemDescription />
</RatingGroup.Item>
<RatingGroup.Control>
<RatingGroup.HiddenInput />
<RatingGroup.Description />
<RatingGroup.ErrorMessage />
</RatingGroup>
```
## Example
Rate Us:
Star Icon
Star Icon
Star Icon
Star Icon
Star Icon
index.tsxstyle.css
```
Copytsx
import { RatingGroup } from "@kobalte/core/rating-group";
import "./style.css";
function App() {
return (
<RatingGroup class="rating-group">
<RatingGroup.Label class="rating-group__label">Rate Us:</RatingGroup.Label>
<RatingGroup.Control class="rating-group__control">
<Index each={Array(5)}>
{_ => (
<RatingGroup.Item class="rating-group-item">
<RatingGroup.ItemControl>
<StarIcon />
</RatingGroup.ItemControl>
</RatingGroup.Item>
)}
</Index>
</RatingGroup.Control>
</RatingGroup>
);
}
```
```
Copytsx
import { RatingGroup } from "@kobalte/core/rating-group";
import "./style.css";
function App() {
return (
<RatingGroup class="rating-group">
<RatingGroup.Label class="rating-group__label">Rate Us:</RatingGroup.Label>
<RatingGroup.Control class="rating-group__control">
<Index each={Array(5)}>
{_ => (
<RatingGroup.Item class="rating-group-item">
<RatingGroup.ItemControl>
<StarIcon />
</RatingGroup.ItemControl>
</RatingGroup.Item>
)}
</Index>
</RatingGroup.Control>
</RatingGroup>
);
}
```
## Usage
### Default value
An initial, uncontrolled value can be provided using the `defaultValue` prop.
Star Icon
Star Icon
Star Icon
Star Icon
Star Icon
```
Copytsx
<RatingGroup defaultValue={3}>
<RatingGroup.Control>
<Index each={Array(5)}>
{_ => (
<RatingGroup.Item>
<RatingGroup.ItemControl>
<StarIcon />
</RatingGroup.ItemControl>
</RatingGroup.Item>
)}
</Index>
</RatingGroup.Control>
</RatingGroup>
```
```
Copytsx
<RatingGroup defaultValue={3}>
<RatingGroup.Control>
<Index each={Array(5)}>
{_ => (
<RatingGroup.Item>
<RatingGroup.ItemControl>
<StarIcon />
</RatingGroup.ItemControl>
</RatingGroup.Item>
)}
</Index>
</RatingGroup.Control>
</RatingGroup>
```
### Controlled value
The `value` prop can be used to make the value controlled. The `onChange` event is fired when the user selects a rating, and receives the new value.
Star Icon
Star Icon
Star Icon
Star Icon
Star Icon
Your rating is: 0/5
```
Copytsx
import { createSignal } from "solid-js";
function ControlledExample() {
const [value, setValue] = createSignal(0);
return (
<>
<RatingGroup value={value()} onChange={setValue}>
<RatingGroup.Control>
<Index each={Array(5)}>
{_ => (
<RatingGroup.Item>
<RatingGroup.ItemControl>
<StarIcon />
</RatingGroup.ItemControl>
</RatingGroup.Item>
)}
</Index>
</RatingGroup.Control>
</RatingGroup>
<p>Your rating is: {value()}/5</p>
</>
);
}
```
```
Copytsx
import { createSignal } from "solid-js";
function ControlledExample() {
const [value, setValue] = createSignal(0);
return (
<>
<RatingGroup value={value()} onChange={setValue}>
<RatingGroup.Control>
<Index each={Array(5)}>
{_ => (
<RatingGroup.Item>
<RatingGroup.ItemControl>
<StarIcon />
</RatingGroup.ItemControl>
</RatingGroup.Item>
)}
</Index>
</RatingGroup.Control>
</RatingGroup>
<p>Your rating is: {value()}/5</p>
</>
);
}
```
### Half Ratings
Allow 0.5 value steps by setting the `allowHalf` prop to true.
Star Icon
Star Icon
Star Icon
Star Icon
Star Icon
```
Copytsx
<RatingGroup allowHalf>
<RatingGroup.Control>
<Index each={Array(5)}>
{_ => (
<RatingGroup.Item>
<RatingGroup.ItemControl>
{(state) => (state.half() ? <StarHalfIcon /> : <StarIcon />)}
</RatingGroup.ItemControl>
</RatingGroup.Item>
)}
</Index>
</RatingGroup.Control>
</RatingGroup>
```
```
Copytsx
<RatingGroup allowHalf>
<RatingGroup.Control>
<Index each={Array(5)}>
{_ => (
<RatingGroup.Item>
<RatingGroup.ItemControl>
{(state) => (state.half() ? <StarHalfIcon /> : <StarIcon />)}
</RatingGroup.ItemControl>
</RatingGroup.Item>
)}
</Index>
</RatingGroup.Control>
</RatingGroup>
```
### Description
The `RatingGroup.Description` component can be used to associate additional help text with a rating group.
Rate Us:
Star Icon
Star Icon
Star Icon
Star Icon
Star Icon
Rate your experience with us.
```
Copytsx
<RatingGroup>
<RatingGroup.Label>Rate Us:</RatingGroup.Label>
<RatingGroup.Control>
<Index each={Array(5)}>
{_ => (
<RatingGroup.Item>
<RatingGroup.ItemControl>
<StarIcon />
</RatingGroup.ItemControl>
</RatingGroup.Item>
)}
</Index>
</RatingGroup.Control>
<RatingGroup.Description>Rate your experience with us.</RatingGroup.Description>
</RatingGroup>
```
```
Copytsx
<RatingGroup>
<RatingGroup.Label>Rate Us:</RatingGroup.Label>
<RatingGroup.Control>
<Index each={Array(5)}>
{_ => (
<RatingGroup.Item>
<RatingGroup.ItemControl>
<StarIcon />
</RatingGroup.ItemControl>
</RatingGroup.Item>
)}
</Index>
</RatingGroup.Control>
<RatingGroup.Description>Rate your experience with us.</RatingGroup.Description>
</RatingGroup>
```
### Error message
The `RatingGroup.ErrorMessage` component can be used to help the user fix a validation error. It should be combined with the `validationState` prop to semantically mark the rating group as invalid for assistive technologies.
By default, it will render only when the `validationState` prop is set to `invalid`, use the `forceMount` prop to always render the error message (ex: for usage with animation libraries).
Rate Us:
Star Icon
Star Icon
Star Icon
Star Icon
Star Icon
Please select a rating between 1 and 5.
```
Copytsx
import { createSignal } from "solid-js";
function ErrorMessageExample() {
const [value, setValue] = createSignal(0);
return (
<RatingGroup
value={value()}
onChange={setValue}
validationState={value() === 0 ? "invalid" : "valid"}
>
<RatingGroup.Label>Rate Us:</RatingGroup.Label>
<RatingGroup.Control>
<Index each={Array(5)}>
{_ => (
<RatingGroup.Item>
<RatingGroup.ItemControl>
<StarIcon />
</RatingGroup.ItemControl>
</RatingGroup.Item>
)}
</Index>
</RatingGroup.Control>
<RatingGroup.ErrorMessage>Please select a rating between 1 and 5.</RatingGroup.ErrorMessage>
</RatingGroup>
);
}
```
```
Copytsx
import { createSignal } from "solid-js";
function ErrorMessageExample() {
const [value, setValue] = createSignal(0);
return (
<RatingGroup
value={value()}
onChange={setValue}
validationState={value() === 0 ? "invalid" : "valid"}
>
<RatingGroup.Label>Rate Us:</RatingGroup.Label>
<RatingGroup.Control>
<Index each={Array(5)}>
{_ => (
<RatingGroup.Item>
<RatingGroup.ItemControl>
<StarIcon />
</RatingGroup.ItemControl>
</RatingGroup.Item>
)}
</Index>
</RatingGroup.Control>
<RatingGroup.ErrorMessage>Please select a rating between 1 and 5.</RatingGroup.ErrorMessage>
</RatingGroup>
);
}
```
### HTML forms
The `name` prop can be used for integration with HTML forms.
Star Icon
Star Icon
Star Icon
Star Icon
Star Icon
ResetSubmit
```
Copytsx
function HTMLFormExample() {
const onSubmit = (e: SubmitEvent) => {
// handle form submission.
};
return (
<form onSubmit={onSubmit}>
<RatingGroup name="rate">
<RatingGroup.Control>
<Index each={Array(5)}>
{_ => (
<RatingGroup.Item>
<RatingGroup.ItemControl>
<StarIcon />
</RatingGroup.ItemControl>
</RatingGroup.Item>
)}
</Index>
</RatingGroup.Control>
<RatingGroup.HiddenInput />
</RatingGroup>
<div>
<button type="reset">Reset</button>
<button type="submit">Submit</button>
</div>
</form>
);
}
```
```
Copytsx
function HTMLFormExample() {
const onSubmit = (e: SubmitEvent) => {
// handle form submission.
};
return (
<form onSubmit={onSubmit}>
<RatingGroup name="rate">
<RatingGroup.Control>
<Index each={Array(5)}>
{_ => (
<RatingGroup.Item>
<RatingGroup.ItemControl>
<StarIcon />
</RatingGroup.ItemControl>
</RatingGroup.Item>
)}
</Index>
</RatingGroup.Control>
<RatingGroup.HiddenInput />
</RatingGroup>
<div>
<button type="reset">Reset</button>
<button type="submit">Submit</button>
</div>
</form>
);
}
```
## API Reference
### RatingGroup
`RatingGroup` is equivalent to the `Root` import from `@kobalte/core/rating-group`.
| Prop | Description |
| --- | --- |
| value | `number`<br> The current rating value. |
| defaultValue | `number`<br> The initial value of the rating group when it is first rendered. Use when you do not need to control the state of the rating group. |
| onChange | `(value: number) => void`<br> Event handler called when the value changes. |
| allowHalf | `boolean`<br> Whether to allow half ratings. |
| orientation | `'horizontal' | 'vertical'`<br> The axis the rating group items should align with. |
| name | `string`<br> The name of the rating group. Submitted with its owning form as part of a name/value pair. |
| validationState | `'valid' | 'invalid'`<br> Whether the rating group should display its "valid" or "invalid" visual styling. |
| required | `boolean`<br> Whether the user must select an item before the owning form can be submitted. |
| disabled | `boolean`<br> Whether the rating group is disabled. |
| readOnly | `boolean`<br> Whether the rating group items can be selected but not changed by the user. |
| Data attribute | Description |
| --- | --- |
| data-valid | Present when the rating group is valid according to the validation rules. |
| data-invalid | Present when the rating group is invalid according to the validation rules. |
| data-required | Present when the user must select a rating group item before the owning form can be submitted. |
| data-disabled | Present when the rating group is disabled. |
| data-readonly | Present when the rating group is read only. |
`RatingGroup.Label`, `RatingGroup.Description` and `RatingGroup.ErrorMesssage` shares the same data-attributes.
### RatingGroup.ErrorMessage
| Prop | Description |
| --- | --- |
| forceMount | `boolean`<br> Used to force mounting when more control is needed. Useful when controlling animation with SolidJS animation libraries. |
### RatingGroup.ItemControl
| Render Prop | Description |
| --- | --- |
| half | `Accessor<boolean>`<br> Whether the rating item is half. |
| highlighted | `Accessor<boolean>`<br> Whether the rating item is highlighted. |
| Data attribute | Description |
| --- | --- |
| data-valid | Present when the parent rating group is valid according to the validation rules. |
| data-invalid | Present when the parent rating group is invalid according to the validation rules. |
| data-required | Present when the parent rating group is required. |
| data-disabled | Present when the parent rating group is disabled. |
| data-readonly | Present when the parent rating group is read only. |
| data-checked | Present when the rating is checked. |
| data-half | Present when the rating is half. |
| data-highlighted | Present when the rating is highlighted. |
`RatingGroup.ItemLabel` and `RatingGroup.ItemDescription` share the same data-attributes.
## Rendered elements
| Component | Default rendered element |
| --- | --- |
| `RatingGroup` | `div` |
| `RatingGroup.Control` | `div` |
| `RatingGroup.Label` | `span` |
| `RatingGroup.HiddenInput` | `input` |
| `RatingGroup.Description` | `div` |
| `RatingGroup.ErrorMessage` | `div` |
| `RatingGroup.Item` | `div` |
| `RatingGroup.ItemControl` | `div` |
| `RatingGroup.ItemLabel` | `label` |
| `RatingGroup.ItemDescription` | `div` |
## Accessibility
### Keyboard Interactions
| Key | Description |
| --- | --- |
| `ArrowDown` | Moves focus to the next item, increasing the rating value based on the `allowHalf` property. |
| `ArrowRight` | Moves focus to the next item, increasing the rating value based on the `allowHalf` property. |
| `ArrowUp` | Moves focus to the previous item, decreasing the rating value based on the `allowHalf` property. |
| `ArrowLeft` | Moves focus to the previous item, decreasing the rating value based on the `allowHalf` property. |
| `Space` | Selects the focused item in the rating group. |
| `Home` | Sets the value of the rating group to 1. |
| `End` | Sets the value of the rating group to the maximum value. |
Previous[←Radio Group](https://kobalte.dev/docs/core/components/radio-group)Next[Search→](https://kobalte.dev/docs/core/components/search)

View File

@ -0,0 +1,562 @@
# Search
Search a searchbox text input with a menu.
Handle the case where dataset filtering needs to occur outside the combobox component.
## Import
```
Copyts
import { Search } from "@kobalte/core/search";
// or
import { Root, Label, ... } from "@kobalte/core/search";
```
```
Copyts
import { Search } from "@kobalte/core/search";
// or
import { Root, Label, ... } from "@kobalte/core/search";
```
## Features
- Inherits all the features of [combobox](https://kobalte.dev/docs/core/components/combobox), except result filtering which should be managed externally.
- Debouncing text input to rate limit search suggestions calls.
- Optional indicator to show when suggestions are loading.
## Anatomy
The search consists of:
- **Search:** The root container for a search component.
- **Search.Label:** The label that gives the user information on the search component.
- **Search.Description:** The description that gives the user more information on the component.
- **Search.Control:** Contains the search input and indicator.
- **Search.Indicator:** Wrapper for icon to indicate loading status.
- **Search.Icon:** A small icon often displayed next to the input as a visual affordance for the fact it can be open.
- **Search.Input:** The input used to search and reflects the selected suggestion values.
- **Search.Portal:** Portals its children into the `body` when the search is open.
- **Search.Content:** Contains the content to be rendered when the search is open.
- **Search.Arrow:** An optional arrow element to render alongside the search content.
- **Search.Listbox:** Contains a list of items and allows a user to search one or more of them.
- **Search.Section:** Used to render the label of an option group. It won't be focusable using arrow keys.
- **Search.Item:** An item of the search suggestion.
- **Search.ItemLabel:** An accessible label to be announced for the item.
- **Search.ItemDescription:** An optional accessible description to be announced for the item.
- **Search.NoResult:** Displayed when no suggestion options are given.
```
Copytsx
<Search>
<Search.Label />
<Search.Control>
<Search.Indicator>
<Search.Icon />
</Search.Indicator>
<Search.Input />
</Search.Control>
<Search.Description />
<Search.Portal>
<Search.Content>
<Search.Arrow />
<Search.Listbox />
<Search.NoResult>
</Search.Content>
</Search.Portal>
</Search>
```
```
Copytsx
<Search>
<Search.Label />
<Search.Control>
<Search.Indicator>
<Search.Icon />
</Search.Indicator>
<Search.Input />
</Search.Control>
<Search.Description />
<Search.Portal>
<Search.Content>
<Search.Arrow />
<Search.Listbox />
<Search.NoResult>
</Search.Content>
</Search.Portal>
</Search>
```
## Example
Magnifying Glass
Emoji selected:
index.tsxstyle.css
```
Copytsx
import { Search } from "@kobalte/core/search";
import { MagnifyingGlassIcon, ReloadIcon } from "some-icon-library";
import { createSignal } from "solid-js";
import "./style.css";
import { queryEmojiData } from "your-search-function";
function App() {
const [options, setOptions] = createSignal([]);
const [emoji, setEmoji] = createSignal();
return (
<>
<Search
triggerMode="focus"
options={options()}
onInputChange={query => setOptions(queryEmojiData(query))}
onChange={result => setEmoji(result)}
optionValue="name"
optionLabel="name"
placeholder="Search an emoji…"
itemComponent={props => (
<Search.Item item={props.item} class="search__item">
<Search.ItemLabel>{props.item.rawValue.emoji}</Search.ItemLabel>
</Search.Item>
)}
>
<Search.Control class="search__control" aria-label="Emoji">
<Search.Indicator
class="search__indicator"
loadingComponent={
<Search.Icon class="load__icon">
<ReloadIcon class="spin__icon" />
</Search.Icon>
}
>
<Search.Icon class="search__icon">
<MagnifyingGlassIcon class="center__icon" />
</Search.Icon>
</Search.Indicator>
<Search.Input class="search__input" />
</Search.Control>
<Search.Portal>
<Search.Content class="search__content" onCloseAutoFocus={(e) => e.preventDefault()}>
<Search.Listbox class="search__listbox" />
<Search.NoResult class="search__no_result">
😬 No emoji found
</Search.NoResult>
</Search.Content>
</Search.Portal>
</Search>
<div class="result__content">
Emoji selected: {emoji()?.emoji} {emoji()?.name}
</div>
</>
)
}
```
```
Copytsx
import { Search } from "@kobalte/core/search";
import { MagnifyingGlassIcon, ReloadIcon } from "some-icon-library";
import { createSignal } from "solid-js";
import "./style.css";
import { queryEmojiData } from "your-search-function";
function App() {
const [options, setOptions] = createSignal([]);
const [emoji, setEmoji] = createSignal();
return (
<>
<Search
triggerMode="focus"
options={options()}
onInputChange={query => setOptions(queryEmojiData(query))}
onChange={result => setEmoji(result)}
optionValue="name"
optionLabel="name"
placeholder="Search an emoji…"
itemComponent={props => (
<Search.Item item={props.item} class="search__item">
<Search.ItemLabel>{props.item.rawValue.emoji}</Search.ItemLabel>
</Search.Item>
)}
>
<Search.Control class="search__control" aria-label="Emoji">
<Search.Indicator
class="search__indicator"
loadingComponent={
<Search.Icon class="load__icon">
<ReloadIcon class="spin__icon" />
</Search.Icon>
}
>
<Search.Icon class="search__icon">
<MagnifyingGlassIcon class="center__icon" />
</Search.Icon>
</Search.Indicator>
<Search.Input class="search__input" />
</Search.Control>
<Search.Portal>
<Search.Content class="search__content" onCloseAutoFocus={(e) => e.preventDefault()}>
<Search.Listbox class="search__listbox" />
<Search.NoResult class="search__no_result">
😬 No emoji found
</Search.NoResult>
</Search.Content>
</Search.Portal>
</Search>
<div class="result__content">
Emoji selected: {emoji()?.emoji} {emoji()?.name}
</div>
</>
)
}
```
## Usage
### Debounce
Set `debounceOptionsMillisecond`, to prevent new search queries immediately on input change. Instead, search queries are requested once input is idle for a set time.
Show a debouncing icon by adding a `loadingComponent` to `Search.Indicator`.
Magnifying Glass
Emoji selected:
```
Copytsx
<Search
triggerMode="focus"
options={options()}
onInputChange={query => setOptions(queryEmojiData(query))}
onChange={result => setEmoji(result)}
debounceOptionsMillisecond={300}
optionValue="name"
optionLabel="name"
placeholder="Search an emoji…"
itemComponent={(props: any) => (
<Search.Item item={props.item}>
<Search.ItemLabel>{props.item.rawValue.emoji}</Search.ItemLabel>
</Search.Item>
)}
>
<Search.Control aria-label="Emoji">
<Search.Indicator
loadingComponent={
<Search.Icon>
<ReloadIcon />
</Search.Icon>
}
>
<Search.Icon>
<MagnifyingGlassIcon />
</Search.Icon>
</Search.Indicator>
<Search.Input />
</Search.Control>
<Search.Portal>
<Search.Content onCloseAutoFocus={(e) => e.preventDefault()}>
<Search.Listbox />
<Search.NoResult>
😬 No emoji found
</Search.NoResult>
</Search.Content>
</Search.Portal>
</Search>
```
```
Copytsx
<Search
triggerMode="focus"
options={options()}
onInputChange={query => setOptions(queryEmojiData(query))}
onChange={result => setEmoji(result)}
debounceOptionsMillisecond={300}
optionValue="name"
optionLabel="name"
placeholder="Search an emoji…"
itemComponent={(props: any) => (
<Search.Item item={props.item}>
<Search.ItemLabel>{props.item.rawValue.emoji}</Search.ItemLabel>
</Search.Item>
)}
>
<Search.Control aria-label="Emoji">
<Search.Indicator
loadingComponent={
<Search.Icon>
<ReloadIcon />
</Search.Icon>
}
>
<Search.Icon>
<MagnifyingGlassIcon />
</Search.Icon>
</Search.Indicator>
<Search.Input />
</Search.Control>
<Search.Portal>
<Search.Content onCloseAutoFocus={(e) => e.preventDefault()}>
<Search.Listbox />
<Search.NoResult>
😬 No emoji found
</Search.NoResult>
</Search.Content>
</Search.Portal>
</Search>
```
### Inline style
To achieve the command menu look, add the `open` prop to permanently open dropdown. Replace `Search.Portal` and `Search.Content` with a `div` to directly mount your content below the search input.
Magnifying Glass
😬 No emoji found
Emoji selected:
```
Copytsx
<Search
open
options={options()}
onInputChange={query => setOptions(queryEmojiData(query))}
onChange={result => setEmoji(result)}
debounceOptionsMillisecond={300}
optionValue="name"
optionLabel="name"
placeholder="Search an emoji…"
itemComponent={(props: any) => (
<Search.Item item={props.item}>
<Search.ItemLabel>{props.item.rawValue.emoji}</Search.ItemLabel>
</Search.Item>
)}
>
<Search.Control aria-label="Emoji">
<Search.Indicator>
<Search.Icon>
<MagnifyingGlassIcon />
</Search.Icon>
</Search.Indicator>
<Search.Input />
</Search.Control>
<div>
<Search.Listbox />
<Search.NoResult>
😬 No emoji found
</Search.NoResult>
</div>
</Search>
```
```
Copytsx
<Search
open
options={options()}
onInputChange={query => setOptions(queryEmojiData(query))}
onChange={result => setEmoji(result)}
debounceOptionsMillisecond={300}
optionValue="name"
optionLabel="name"
placeholder="Search an emoji…"
itemComponent={(props: any) => (
<Search.Item item={props.item}>
<Search.ItemLabel>{props.item.rawValue.emoji}</Search.ItemLabel>
</Search.Item>
)}
>
<Search.Control aria-label="Emoji">
<Search.Indicator>
<Search.Icon>
<MagnifyingGlassIcon />
</Search.Icon>
</Search.Indicator>
<Search.Input />
</Search.Control>
<div>
<Search.Listbox />
<Search.NoResult>
😬 No emoji found
</Search.NoResult>
</div>
</Search>
```
## API Reference
### Search
`Search` is equivalent to the `Root` import from `@kobalte/core/search`.
| Prop | Description |
| --- | --- |
| options | `Array<T | U>`<br> An array of options to display as the available options. |
| optionValue | `keyof T | ((option: T) => string | number)`<br> Property name or getter function to use as the value of an option. This is the value that will be submitted when the search component is part of a `<form>`. |
| optionTextValue | `keyof T | ((option: T) => string)`<br> Property name or getter function to use as the text value of an option for typeahead purpose. |
| optionLabel | `keyof T | ((option: T) => string)`<br> Property name or getter function to use as the label of an option. This is the string representation of the option to display in the `Search.Input`. |
| optionDisabled | `keyof T | ((option: T) => boolean)`<br> Property name or getter function to use as the disabled flag of an option. |
| optionGroupChildren | `keyof U`<br> Property name that refers to the children options of an option group. |
| itemComponent | `Component<SearchItemComponentProps<T>>`<br> When NOT virtualized, the component to render as an item in the `Search.Listbox`. |
| sectionComponent | `Component<SearchSectionComponentProps<U>>`<br> When NOT virtualized, the component to render as a section in the `Search.Listbox`. |
| multiple | `boolean`<br> Whether the search component allows multi-selection. |
| placeholder | `JSX.Element`<br> The content that will be rendered when no value or defaultValue is set. |
| value | `T | Array<T>`<br> The controlled value of the search input. |
| defaultValue | `T | Array<T>`<br> The value of the search input when initially rendered. Useful when you do not need to control the value. |
| onChange | `(value: T | Array<T>) => void`<br> Event handler called when the value changes. |
| open | `boolean`<br> The controlled open state of the search suggestion. |
| defaultOpen | `boolean`<br> The default open state when initially rendered. Useful when you do not need to control the open state. |
| onOpenChange | `(open: boolean, triggerMode?: SearchTriggerMode) => void`<br> Event handler called when the open state of the search component changes. Returns the new open state and the action that caused the opening of the menu. |
| onInputChange | `(value: string) => void`<br> Handler that is called when the search input value changes. |
| triggerMode | `SearchTriggerMode`<br> The interaction required to display search suggestion, it can be one of the following: <br> \- **input**: open search suggestion when the user is typing. <br> \- **focus**: open search suggestion when the input is focused. <br> \- **manual**: open search suggestion when pressing arrow down/up while focus is on the input or clicking on the trigger. |
| removeOnBackspace | `boolean`<br> When `multiple` is true, whether the last selected option should be removed when the user press the Backspace key and the input is empty. |
| allowDuplicateSelectionEvents | `boolean`<br> Whether `onChange` should fire even if the new value is the same as the last. |
| disallowEmptySelection | `boolean`<br> Whether the search component allows empty selection or not. |
| allowsEmptyCollection | `boolean`<br> Whether the search component allows the menu to be open when the collection is empty. |
| closeOnSelection | `boolean`<br> Whether the search component closes after selection. |
| selectionBehavior | `'toggle' | 'replace'`<br> How selection should behave in the search component. |
| virtualized | `boolean`<br> Whether the search suggestion uses virtual scrolling. |
| modal | `boolean`<br> Whether the search component should be the only visible content for screen readers, when set to `true`: <br> \- interaction with outside elements will be disabled. <br> \- scroll will be locked. <br> \- focus will be locked inside the search component content. <br> \- elements outside the search component content will not be visible for screen readers. |
| preventScroll | `boolean`<br> Whether the scroll should be locked even if the search suggestion is not modal. |
| forceMount | `boolean`<br> Used to force mounting the search suggestion (portal, positioner and content) when more control is needed. Useful when controlling animation with SolidJS animation libraries. |
| name | `string`<br> The name of the search component. Submitted with its owning form as part of a name/value pair. |
| validationState | `'valid' | 'invalid'`<br> Whether the search component should display its "valid" or "invalid" visual styling. |
| required | `boolean`<br> Whether the user must select an item before the owning form can be submitted. |
| disabled | `boolean`<br> Whether the search component is disabled. |
| readOnly | `boolean`<br> Whether the search component items can be selected but not changed by the user. |
| autoComplete | `string`<br> Describes the type of autocomplete functionality the input should provide if any. See [MDN](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#htmlattrdefautocomplete) |
| translations | [`SearchIntlTranslations`](https://github.com/kobaltedev/kobalte/blob/main/packages/core/src/combobox/combobox.intl.ts)<br> Localization strings. |
`Search` also accepts the following props to customize the placement of the `Search.Content`.
| Prop | Description |
| --- | --- |
| placement | `Placement`<br> The placement of the search component content. |
| gutter | `number`<br> The distance between the search component content and the trigger element. |
| shift | `number`<br> The skidding of the search component content along the trigger element. |
| flip | `boolean | string`<br> Controls the behavior of the search component content when it overflows the viewport: <br> \- If a `boolean`, specifies whether the search component content should flip to the opposite side when it overflows.<br> \- If a `string`, indicates the preferred fallback placements when it overflows.<br>The placements must be spaced-delimited, e.g. "top left". |
| slide | `boolean`<br> Whether the search component content should slide when it overflows. |
| overlap | `boolean`<br> Whether the search component content can overlap the trigger element when it overflows. |
| sameWidth | `boolean`<br> Whether the search component content should have the same width as the trigger element. This will be exposed to CSS as `--kb-popper-anchor-width`. |
| fitViewport | `boolean`<br> Whether the search component content should fit the viewport. If this is set to true, the search component content will have `maxWidth` and `maxHeight` set to the viewport size. This will be exposed to CSS as `--kb-popper-available-width` and `--kb-popper-available-height`. |
| hideWhenDetached | `boolean`<br> Whether to hide the search component content when the trigger element becomes occluded. |
| detachedPadding | `number`<br> The minimum padding in order to consider the trigger element occluded. |
| arrowPadding | `number`<br> The minimum padding between the arrow and the search component content corner. |
| overflowPadding | `number`<br> The minimum padding between the search component content and the viewport edge. This will be exposed to CSS as `--kb-popper-overflow-padding`. |
| Data attribute | Description |
| --- | --- |
| data-valid | Present when the search component is valid according to the validation rules. |
| data-invalid | Present when the search component is invalid according to the validation rules. |
| data-required | Present when the user must select an item before the owning form can be submitted. |
| data-disabled | Present when the search component is disabled. |
| data-readonly | Present when the search component is read only. |
`Search.Label`, `Search.Control`, `Search.Input`, `Search.Trigger`, `Search.Description` and `Search.ErrorMesssage` shares the same data-attributes.
### Search.Control
| Render Prop | Description |
| --- | --- |
| selectedOptions | `Accessor<T[]>`<br> An array of selected options. |
| remove | `(option: T) => void`<br> A function to remove an option from the selection. |
| clear | `() => void`<br> A function to clear the selection. |
### Search.Indicator
| Prop | Description |
| --- | --- |
| loadingComponent | `JSX.Element`<br> The component that is displayed when suggestion options are being fetched. |
### Search.Icon
| Data attribute | Description |
| --- | --- |
| data-expanded | Present when the search component is open. |
| data-closed | Present when the search component is close. |
### Search.Content
| Data attribute | Description |
| --- | --- |
| data-expanded | Present when the search component is open. |
| data-closed | Present when the search component is close. |
### Search.Arrow
| Prop | Description |
| --- | --- |
| size | `number`<br> The size of the arrow. |
### Search.Listbox
| Prop | Description |
| --- | --- |
| scrollRef | `Accessor<HTMLElement | undefined>`<br> The ref attached to the scrollable element, used to provide automatic scrolling on item focus. If not provided, defaults to the listbox. |
| scrollToItem | `(key: string) => void`<br> When virtualized, the Virtualizer function used to scroll to the item of the given key. |
| children | `(items: Accessor<Collection<CollectionNode<T | U>>>) => JSX.Element`<br> When virtualized, a map function that receives an _items_ signal representing all items and sections. |
### Search.Item
| Prop | Description |
| --- | --- |
| item | `CollectionNode`<br> The collection node to render. |
| Data attribute | Description |
| --- | --- |
| data-disabled | Present when the item is disabled. |
| data-selected | Present when the item is selected. |
| data-highlighted | Present when the item is highlighted. |
`Search.ItemLabel` and `Search.ItemDescription` shares the same data-attributes.
## Rendered elements
| Component | Default rendered element |
| --- | --- |
| `Search` | `div` |
| `Search.Label` | `span` |
| `Search.Description` | `div` |
| `Search.Control` | `div` |
| `Search.Indicator` | `div` |
| `Search.Icon` | `span` |
| `Search.Input` | `input` |
| `Search.Portal` | `Portal` |
| `Search.Content` | `div` |
| `Search.Arrow` | `div` |
| `Search.Listbox` | `ul` |
| `Search.Section` | `li` |
| `Search.Item` | `li` |
| `Search.ItemLabel` | `div` |
| `Search.ItemDescription` | `div` |
| `Search.NoResult` | `div` |
## Accessibility
### Keyboard Interactions
| Key | Description |
| --- | --- |
| `Enter` | When focus is virtualy on an item, selects the focused item. |
| `ArrowDown` | When focus is on the input, opens the search suggestion and virtual focuses the first or selected item. <br> When focus is virtualy on an item, moves virtual focus to the next item. |
| `ArrowUp` | When focus is on the input, opens the search suggestion and virtual focuses the last or selected item. <br> When focus is virtualy on an item, moves virtual focus to the previous item. |
| `Alt` \+ `ArrowDown` | When focus is on the input, opens the search suggestion. |
| `Alt` \+ `ArrowUp` | When focus is on the input, closes the search suggestion. |
| `Home` | When focus is on the input, moves virtual focus to first item. |
| `End` | When focus is on the input, moves virtual focus to last item. |
| `Esc` | When the search suggestion is open, closes the search suggestion. <br> When the search suggestion is closed, clear the input and selection. |
Previous[←Rating Group](https://kobalte.dev/docs/core/components/rating-group)Next[Segmented Control→](https://kobalte.dev/docs/core/components/segmented-control)

View File

@ -0,0 +1,560 @@
# Segmented Control
A linear set of two or more segments, each of which functions as a radio input, and a default value is required.
## Import
```
Copyts
import { SegmentedControl } from "@kobalte/core/segmented-control";
// or
import { Root, Label, ... } from "@kobalte/core/segmented-control";
```
```
Copyts
import { SegmentedControl } from "@kobalte/core/segmented-control";
// or
import { Root, Label, ... } from "@kobalte/core/segmented-control";
```
## Features
- Follow the [WAI ARIA Radio Group](https://www.w3.org/WAI/ARIA/apg/patterns/radiobutton/) design pattern.
- Each segment is built with a native HTML `<input>` element, which is visually hidden to allow custom styling.
- Syncs with form reset events.
- Group and segment labeling support for assistive technology.
- Can be controlled or uncontrolled.
- Supports animatable indicator.
## Anatomy
The segmented control consists of:
- **SegmentedControl**: The root container for the segmented control.
- **SegmentedControl.Label**: The label that gives the user information on the segmented control.
- **SegmentedControl.Description**: The description that gives the user more information on the segmented control.
- **SegmentedControl.ErrorMessage**: The error message that gives the user information about how to fix a validation error on the segmented control.
- **SegmentedControl.Indicator:** The visual indicator displayed below the items to indicate the selected item.
The segmented control item consists of:
- **SegmentedControl.Item**: The root container for an item's radio button.
- **SegmentedControl.ItemInput**: The native html input that is visually hidden in the item's radio button.
- **SegmentedControl.ItemControl**: The element that visually represents an item's radio button.
- **SegmentedControl.ItemIndicator**: The visual indicator rendered when the item's radio button is in a checked state.
- **SegmentedControl.ItemLabel**: The label that gives the user information on the item's radio button.
- **SegmentedControl.ItemDescription**: The description that gives the user more information on the item's radio button.
```
Copytsx
<SegmentedControl>
<SegmentedControl.Label />
<SegmentedControl.Indicator />
<SegmentedControl.Item>
<SegmentedControl.ItemInput />
<SegmentedControl.ItemControl>
<SegmentedControl.ItemIndicator />
</SegmentedControl.ItemControl>
<SegmentedControl.ItemLabel />
<SegmentedControl.ItemDescription />
</SegmentedControl.Item>
<SegmentedControl.Description />
<SegmentedControl.ErrorMessage />
</SegmentedControl>
```
```
Copytsx
<SegmentedControl>
<SegmentedControl.Label />
<SegmentedControl.Indicator />
<SegmentedControl.Item>
<SegmentedControl.ItemInput />
<SegmentedControl.ItemControl>
<SegmentedControl.ItemIndicator />
</SegmentedControl.ItemControl>
<SegmentedControl.ItemLabel />
<SegmentedControl.ItemDescription />
</SegmentedControl.Item>
<SegmentedControl.Description />
<SegmentedControl.ErrorMessage />
</SegmentedControl>
```
## Example
Favorite fruit
Apple
Orange
Watermelon
index.tsxstyle.css
```
Copytsx
import { SegmentedControl } from "@kobalte/core/segmented-control";
import "./style.css";
function App() {
return (
<SegmentedControl class={style["segmented-control"]} defaultValue="Apple">
<SegmentedControl.Label class={style["segmented-control__label"]}>
Favorite fruit
</SegmentedControl.Label>
<div class={style["segmented-control__wrapper"]} role="presentation">
<SegmentedControl.Indicator class={style["segmented-control__indicator"]} />
<div class={style["segmented-control__items"]} role="presentation">
<For each={["Apple", "Orange", "Watermelon"]}>
{(fruit) => (
<SegmentedControl.Item value={fruit} class={style["segmented-control__item"]}>
<SegmentedControl.ItemInput class={style["segmented-control__item-input"]} />
<SegmentedControl.ItemLabel class={style["segmented-control__item-label"]}>
{fruit}
</SegmentedControl.ItemLabel>
</SegmentedControl.Item>
)}
</For>
</div>
</div>
</SegmentedControl>
);
}
```
```
Copytsx
import { SegmentedControl } from "@kobalte/core/segmented-control";
import "./style.css";
function App() {
return (
<SegmentedControl class={style["segmented-control"]} defaultValue="Apple">
<SegmentedControl.Label class={style["segmented-control__label"]}>
Favorite fruit
</SegmentedControl.Label>
<div class={style["segmented-control__wrapper"]} role="presentation">
<SegmentedControl.Indicator class={style["segmented-control__indicator"]} />
<div class={style["segmented-control__items"]} role="presentation">
<For each={["Apple", "Orange", "Watermelon"]}>
{(fruit) => (
<SegmentedControl.Item value={fruit} class={style["segmented-control__item"]}>
<SegmentedControl.ItemInput class={style["segmented-control__item-input"]} />
<SegmentedControl.ItemLabel class={style["segmented-control__item-label"]}>
{fruit}
</SegmentedControl.ItemLabel>
</SegmentedControl.Item>
)}
</For>
</div>
</div>
</SegmentedControl>
);
}
```
## Usage
The `role="presentation"` is required for all non content elements between the `SegmentedControl` and `SegmentedControl.Item` due to a bug in Chromium based browsers that incorrectly parse semantics and break screen readers.
### Controlled value
The `value` prop, which accepts a value corresponding with the `value` prop of each radio, can be used to make the value controlled. The `onChange` event is fired when the user selects a radio, and receives the new value.
Favorite fruit
Apple
Orange
Watermelon
Your favorite fruit is: Orange.
```
Copytsx
import { createSignal } from "solid-js";
function ControlledExample() {
const [value, setValue] = createSignal("Orange");
return (
<>
<SegmentedControl value={value()} onChange={setValue}>
<SegmentedControl.Label>Favorite fruit</SegmentedControl.Label>
<div role="presentation">
<SegmentedControl.Indicator />
<div role="presentation">
<For each={["Apple", "Orange", "Watermelon"]}>
{fruit => (
<SegmentedControl.Item value={fruit}>
<SegmentedControl.ItemInput />
<SegmentedControl.ItemLabel>{fruit}</SegmentedControl.ItemLabel>
</SegmentedControl.Item>
)}
</For>
</div>
</div>
</SegmentedControl>
<p class="not-prose text-sm mt-4">Your favorite fruit is: {value()}.</p>
</>
);
}
```
```
Copytsx
import { createSignal } from "solid-js";
function ControlledExample() {
const [value, setValue] = createSignal("Orange");
return (
<>
<SegmentedControl value={value()} onChange={setValue}>
<SegmentedControl.Label>Favorite fruit</SegmentedControl.Label>
<div role="presentation">
<SegmentedControl.Indicator />
<div role="presentation">
<For each={["Apple", "Orange", "Watermelon"]}>
{fruit => (
<SegmentedControl.Item value={fruit}>
<SegmentedControl.ItemInput />
<SegmentedControl.ItemLabel>{fruit}</SegmentedControl.ItemLabel>
</SegmentedControl.Item>
)}
</For>
</div>
</div>
</SegmentedControl>
<p class="not-prose text-sm mt-4">Your favorite fruit is: {value()}.</p>
</>
);
}
```
### Description
The `SegmentedControl.Description` component can be used to associate additional help text with a segmented control.
Favorite fruit
Apple
Orange
Watermelon
Choose the fruit you like the most.
```
Copytsx
<SegmentedControl defaultValue="Apple">
<SegmentedControl.Label>Favorite fruit</SegmentedControl.Label>
<div role="presentation">
<SegmentedControl.Indicator />
<div role="presentation">
<For each={["Apple", "Orange", "Watermelon"]}>
{fruit => <SegmentedControl.Item value={fruit}>...</SegmentedControl.Item>}
</For>
</div>
</div>
<SegmentedControl.Description>Choose the fruit you like the most.</SegmentedControl.Description>
</SegmentedControl>
```
```
Copytsx
<SegmentedControl defaultValue="Apple">
<SegmentedControl.Label>Favorite fruit</SegmentedControl.Label>
<div role="presentation">
<SegmentedControl.Indicator />
<div role="presentation">
<For each={["Apple", "Orange", "Watermelon"]}>
{fruit => <SegmentedControl.Item value={fruit}>...</SegmentedControl.Item>}
</For>
</div>
</div>
<SegmentedControl.Description>Choose the fruit you like the most.</SegmentedControl.Description>
</SegmentedControl>
```
### Error message
The `SegmentedControl.ErrorMessage` component can be used to help the user fix a validation error. It should be combined with the `validationState` prop to semantically mark the segmented control as invalid for assistive technologies.
By default, it will render only when the `validationState` prop is set to `invalid`, use the `forceMount` prop to always render the error message (ex: for usage with animation libraries).
Favorite fruit
Apple
Orange
Watermelon
Hmm, I prefer apples.
```
Copytsx
import { createSignal } from "solid-js";
function ErrorMessageExample() {
const [value, setValue] = createSignal("Orange");
return (
<SegmentedControl
value={value()}
onChange={setValue}
validationState={value() !== "Apple" ? "invalid" : "valid"}
>
<SegmentedControl.Label>Favorite fruit</SegmentedControl.Label>
<div role="presentation">
<SegmentedControl.Indicator />
<div role="presentation">
<For each={["Apple", "Orange", "Watermelon"]}>
{fruit => <SegmentedControl.Item value={fruit}>...</SegmentedControl.Item>}
</For>
</div>
</div>
<SegmentedControl.ErrorMessage>Hmm, I prefer apples.</SegmentedControl.ErrorMessage>
</SegmentedControl>
);
}
```
```
Copytsx
import { createSignal } from "solid-js";
function ErrorMessageExample() {
const [value, setValue] = createSignal("Orange");
return (
<SegmentedControl
value={value()}
onChange={setValue}
validationState={value() !== "Apple" ? "invalid" : "valid"}
>
<SegmentedControl.Label>Favorite fruit</SegmentedControl.Label>
<div role="presentation">
<SegmentedControl.Indicator />
<div role="presentation">
<For each={["Apple", "Orange", "Watermelon"]}>
{fruit => <SegmentedControl.Item value={fruit}>...</SegmentedControl.Item>}
</For>
</div>
</div>
<SegmentedControl.ErrorMessage>Hmm, I prefer apples.</SegmentedControl.ErrorMessage>
</SegmentedControl>
);
}
```
### HTML forms
The segmented control `name` prop, paired with the item's radio `value` prop, can be used for integration with HTML forms.
Favorite fruit
Apple
Orange
Watermelon
ResetSubmit
```
Copytsx
function HTMLFormExample() {
const onSubmit = (e: SubmitEvent) => {
// handle form submission.
};
return (
<form onSubmit={onSubmit}>
<SegmentedControl name="favorite-fruit" defaultValue="Orange">
<SegmentedControl.Label>Favorite fruit</SegmentedControl.Label>
<div role="presentation">
<SegmentedControl.Indicator />
<div role="presentation">
<For each={["Apple", "Orange", "Watermelon"]}>
{fruit => <SegmentedControl.Item value={fruit}>...</SegmentedControl.Item>}
</For>
</div>
</div>
</SegmentedControl>
<div>
<button type="reset">Reset</button>
<button type="submit">Submit</button>
</div>
</form>
);
}
```
```
Copytsx
function HTMLFormExample() {
const onSubmit = (e: SubmitEvent) => {
// handle form submission.
};
return (
<form onSubmit={onSubmit}>
<SegmentedControl name="favorite-fruit" defaultValue="Orange">
<SegmentedControl.Label>Favorite fruit</SegmentedControl.Label>
<div role="presentation">
<SegmentedControl.Indicator />
<div role="presentation">
<For each={["Apple", "Orange", "Watermelon"]}>
{fruit => <SegmentedControl.Item value={fruit}>...</SegmentedControl.Item>}
</For>
</div>
</div>
</SegmentedControl>
<div>
<button type="reset">Reset</button>
<button type="submit">Submit</button>
</div>
</form>
);
}
```
### Orientation
The segmented control `orientation` prop can be used to change the segmented control's orientation.
Favorite fruit
Apple
Orange
Watermelon
```
Copytsx
<SegmentedControl orientation="vertical" defaultValue="Apple">
<SegmentedControl.Label>Favorite fruit</SegmentedControl.Label>
<div role="presentation">
<SegmentedControl.Indicator />
<div role="presentation">
<For each={["Apple", "Orange", "Watermelon"]}>
{fruit => <SegmentedControl.Item value={fruit}>...</SegmentedControl.Item>}
</For>
</div>
</div>
<SegmentedControl.Description>Choose the fruit you like the most.</SegmentedControl.Description>
</SegmentedControl>
```
```
Copytsx
<SegmentedControl orientation="vertical" defaultValue="Apple">
<SegmentedControl.Label>Favorite fruit</SegmentedControl.Label>
<div role="presentation">
<SegmentedControl.Indicator />
<div role="presentation">
<For each={["Apple", "Orange", "Watermelon"]}>
{fruit => <SegmentedControl.Item value={fruit}>...</SegmentedControl.Item>}
</For>
</div>
</div>
<SegmentedControl.Description>Choose the fruit you like the most.</SegmentedControl.Description>
</SegmentedControl>
```
## API Reference
### SegmentedControl
`SegmentedControl` is equivalent to the `Root` import from `@kobalte/core/segmented-control`.
| Prop | Description |
| --- | --- |
| value | `string`<br> The controlled value of the item's radio button to check. |
| defaultValue | `string`<br> The value of the item's radio button that should be checked when initially rendered. Useful when you do not need to control the state of the radio buttons. |
| onChange | `(value: string) => void`<br> Event handler called when the value changes. |
| orientation | `'horizontal' | 'vertical'`<br> The axis the segmented control items should align with. |
| name | `string`<br> The name of the segmented control. Submitted with its owning form as part of a name/value pair. |
| validationState | `'valid' | 'invalid'`<br> Whether the segmented control should display its "valid" or "invalid" visual styling. |
| required | `boolean`<br> Whether the user must check a segmented control item before the owning form can be submitted. |
| disabled | `boolean`<br> Whether the segmented control is disabled. |
| readOnly | `boolean`<br> Whether the segmented control items can be selected but not changed by the user. |
| Data attribute | Description |
| --- | --- |
| data-valid | Present when the segmented control is valid according to the validation rules. |
| data-invalid | Present when the segmented control is invalid according to the validation rules. |
| data-required | Present when the user must check a segmented control item before the owning form can be submitted. |
| data-disabled | Present when the segmented control is disabled. |
| data-readonly | Present when the segmented control is read only. |
`SegmentedControl.Label`, `SegmentedControl.Description` and `SegmentedControl.ErrorMesssage` shares the same data-attributes.
### SegmentedControl.ErrorMessage
| Prop | Description |
| --- | --- |
| forceMount | `boolean`<br> Used to force mounting when more control is needed. Useful when controlling animation with SolidJS animation libraries. |
### SegmentedControl.Item
| Prop | Description |
| --- | --- |
| value | `string`<br> The value of the item's radio button, used when submitting an HTML form. See [MDN](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/radio#Value). |
| disabled | `boolean`<br> Whether the item's radio button is disabled or not. |
| Data attribute | Description |
| --- | --- |
| data-valid | Present when the parent segmented control is valid according to the validation rules. |
| data-invalid | Present when the parent segmented control is invalid according to the validation rules. |
| data-checked | Present when the segmented control checked. |
| data-disabled | Present when the segmented control disabled. |
`SegmentedControl.ItemInput`, `SegmentedControl.ItemControl`, `SegmentedControl.ItemIndicator` and `SegmentedControl.ItemLabel` shares the same data-attributes.
### SegmentedControl.ItemIndicator
| Prop | Description |
| --- | --- |
| forceMount | `boolean`<br> Used to force mounting when more control is needed. Useful when controlling animation with SolidJS animation libraries. |
## Rendered elements
| Component | Default rendered element |
| --- | --- |
| `SegmentedControl` | `div` |
| `SegmentedControl.Label` | `span` |
| `SegmentedControl.Description` | `div` |
| `SegmentedControl.ErrorMessage` | `div` |
| `SegmentedControl.Indicator` | `div` |
| `SegmentedControl.Item` | `div` |
| `SegmentedControl.ItemInput` | `input` |
| `SegmentedControl.ItemControl` | `div` |
| `SegmentedControl.ItemIndicator` | `div` |
| `SegmentedControl.ItemLabel` | `label` |
| `SegmentedControl.ItemDescription` | `div` |
## Accessibility
### Keyboard Interactions
| Key | Description |
| --- | --- |
| `Tab` | Moves focus to either the checked item's radio button or the first item's radio button in the group. |
| `Space` | When focus is on an unchecked item's radio button, checks it. |
| `ArrowDown` | Moves focus and checks the next item's radio button in the group. |
| `ArrowRight` | Moves focus and checks the next item's radio button in the group. |
| `ArrowUp` | Moves focus and checks the previous item's radio button in the group. |
| `ArrowLeft` | Moves focus and checks the previous item's radio button in the group. |
Previous[←Search](https://kobalte.dev/docs/core/components/search)Next[Select→](https://kobalte.dev/docs/core/components/select)

View File

@ -1,10 +0,0 @@
# Server side rendering
## Usage with SolidStart
Kobalte works out of the box with [SolidStart](https://start.solidjs.com/).
Kobalte has been tested with `solid-js@1.8.15` and `@solidjs/start@0.6.1`, compatibility with
other versions is not guaranteed.
Previous[←Polymorphism](https://kobalte.dev/docs/core/overview/polymorphism)Next[Accordion→](https://kobalte.dev/docs/core/components/accordion)

View File

@ -0,0 +1,648 @@
# Time Field
A time field allows users to enter and edit time values using a keyboard. Each part of a time value is displayed in an individually editable segment.
## Import
```
Copyts
import { TimeField } from "@kobalte/core/time-field";
// or
import { Root, Label, ... } from "@kobalte/core/time-field";
```
```
Copyts
import { TimeField } from "@kobalte/core/time-field";
// or
import { Root, Label, ... } from "@kobalte/core/time-field";
```
## Features
- Times can optionally include a time zone. All modifications follow time zone rules such as daylight saving time.
- Support for locale-specific formatting, number systems, hour cycles, and right-to-left layout.
- Each time unit is displayed as an individually focusable and editable segment, which allows users an easy way to edit times using the keyboard, in any format and locale.
- Time segments are editable using an easy to use numeric keypad, and all interactions are accessible using touch-based screen readers.
- Can be controlled or uncontrolled.
- Integrates with HTML forms.
## Anatomy
The time field consists of:
- **TimeField**: The root container for the time field.
- **TimeField.Label**: The label that gives the user information on the time field.
- **TimeField.Field**: The container for the segments.
- **TimeField.Segment**: The component that represents a unit of a time.
- **TimeField.Description**: The description that gives the user more information on the time field.
- **TimeField.ErrorMessage**: The error message that gives the user information about how to fix a validation error on the time field.
- **TimeField.HiddenInput**: The native html input that is visually hidden in the time field.
```
Copytsx
<TimeField>
<TimeField.Label />
<TimeField.Field>
<TimeField.Segment />
</TimeField.Field>
<TimeField.Description />
<TimeField.ErrorMessage />
<TimeField.HiddenInput />
</TimeField>
```
```
Copytsx
<TimeField>
<TimeField.Label />
<TimeField.Field>
<TimeField.Segment />
</TimeField.Field>
<TimeField.Description />
<TimeField.ErrorMessage />
<TimeField.HiddenInput />
</TimeField>
```
## Example
Event time
:
AM
index.tsxstyle.css
```
Copytsx
import { TimeField } from "@kobalte/core/time-field";
import "./style.css";
function App() {
return (
<TimeField class="time-field">
<TimeField.Label class="time-field__label">Event time</TimeField.Label>
<TimeField.Field class="time-field__field">
{segment => <TimeField.Segment class="time-field__segment" segment={segment()} />}
</TimeField.Field>
</TimeField>
);
}
```
```
Copytsx
import { TimeField } from "@kobalte/core/time-field";
import "./style.css";
function App() {
return (
<TimeField class="time-field">
<TimeField.Label class="time-field__label">Event time</TimeField.Label>
<TimeField.Field class="time-field__field">
{segment => <TimeField.Segment class="time-field__segment" segment={segment()} />}
</TimeField.Field>
</TimeField>
);
}
```
## Usage
### Default Value
A TimeField displays a placeholder by default. An initial, uncontrolled value can be provided to the TimeField using the `defaultValue` prop.
Time values are provided using objects in the [@internationalized/date](https://react-spectrum.adobe.com/internationalized/date/) package. This library handles correct international date and time manipulation across calendars, time zones, and other localization concerns.
11
:
45
AM
Selected Time: 11:45 AM
```
Copytsx
import { Time } from "@internationalized/date";
<TimeField defaultValue={new Time(11, 45)}>
<TimeField.Field>
{(segment) => (
<TimeField.Segment segment={segment()} />
)}
</TimeField.Field>
</TimeField>
```
```
Copytsx
import { Time } from "@internationalized/date";
<TimeField defaultValue={new Time(11, 45)}>
<TimeField.Field>
{(segment) => (
<TimeField.Segment segment={segment()} />
)}
</TimeField.Field>
</TimeField>
```
### Controlled Value
The `value` prop can be used to make the value controlled. The `onChange` event is fired when the time value changes.
9
:
45
AM
Selected Time: 9:45 AM
Selected time: 9:45 AM
```
Copytsx
import { createSignal } from "solid-js";
import { createDateFormatter } from "@kobalte/core/i18n";
import { getLocalTimeZone, Time, toCalendarDateTime, today } from "@internationalized/date";
function ControlledValueExample() {
const [value, setValue] = createSignal(new Time(9, 45));
const dateFormatter = createDateFormatter({
hour12: true,
timeStyle: "short",
});
return (
<>
<TimeField value={value()} onChange={setValue}>
<TimeField.Field>{segment => <TimeField.Segment segment={segment()} />}</TimeField.Field>
</TimeField>
<p>
Selected time:{" "}
{value()
? dateFormatter().format(
toCalendarDateTime(today(getLocalTimeZone()), value()).toDate(getLocalTimeZone()),
)
: ""}
</p>
</>
);
}
```
```
Copytsx
import { createSignal } from "solid-js";
import { createDateFormatter } from "@kobalte/core/i18n";
import { getLocalTimeZone, Time, toCalendarDateTime, today } from "@internationalized/date";
function ControlledValueExample() {
const [value, setValue] = createSignal(new Time(9, 45));
const dateFormatter = createDateFormatter({
hour12: true,
timeStyle: "short",
});
return (
<>
<TimeField value={value()} onChange={setValue}>
<TimeField.Field>{segment => <TimeField.Segment segment={segment()} />}</TimeField.Field>
</TimeField>
<p>
Selected time:{" "}
{value()
? dateFormatter().format(
toCalendarDateTime(today(getLocalTimeZone()), value()).toDate(getLocalTimeZone()),
)
: ""}
</p>
</>
);
}
```
### Time Zones
TimeField is time zone aware when a `ZonedDateTime` object is provided as the value. In this case, the time zone abbreviation is displayed, and time zone concerns such as daylight saving time are taken into account when the value is manipulated.
[@internationalized/date](https://react-spectrum.adobe.com/internationalized/date/) includes functions for parsing strings in multiple formats into `ZonedDateTime` objects.
12
:
45
AM
PST
Selected Time: 12:45 AM PST
```
Copytsx
import { parseZonedDateTime } from "@internationalized/date";
<TimeField defaultValue={parseZonedDateTime("2022-11-07T00:45[America/Los_Angeles]")}>
<TimeField.Field>{segment => <TimeField.Segment segment={segment()} />}</TimeField.Field>
</TimeField>;
```
```
Copytsx
import { parseZonedDateTime } from "@internationalized/date";
<TimeField defaultValue={parseZonedDateTime("2022-11-07T00:45[America/Los_Angeles]")}>
<TimeField.Field>{segment => <TimeField.Segment segment={segment()} />}</TimeField.Field>
</TimeField>;
```
### Granularity
The `granularity` prop allows you to control the smallest unit that is displayed by a TimeField. By default, times are displayed with "minute" granularity. More granular time values can be displayed by setting the `granularity` prop to "second".
:
:
AM
```
Copytsx
<TimeField granularity="second">
<TimeField.Field>{segment => <TimeField.Segment segment={segment()} />}</TimeField.Field>
</TimeField>
```
```
Copytsx
<TimeField granularity="second">
<TimeField.Field>{segment => <TimeField.Segment segment={segment()} />}</TimeField.Field>
</TimeField>
```
### Minimum and Maximum Values
The `minValue` and `maxValue` props can be used to perform builtin validation. This marks the time field as invalid using ARIA if the user enters an invalid time.
9
:
45
AM
Selected Time: 9:45 AM
```
Copytsx
<TimeField defaultValue={new Time(9, 45)} minValue={new Time(9)} maxValue={new Time(17)}>
<TimeField.Field>{segment => <TimeField.Segment segment={segment()} />}</TimeField.Field>
<TimeField.ErrorMessage>Select time between 9 AM and 5 PM.</TimeField.ErrorMessage>
</TimeField>
```
```
Copytsx
<TimeField defaultValue={new Time(9, 45)} minValue={new Time(9)} maxValue={new Time(17)}>
<TimeField.Field>{segment => <TimeField.Segment segment={segment()} />}</TimeField.Field>
<TimeField.ErrorMessage>Select time between 9 AM and 5 PM.</TimeField.ErrorMessage>
</TimeField>
```
### Placeholder Value
When no value is set, a placeholder is shown. The format of the placeholder is influenced by the `granularity` and `placeholderValue` props. `placeholderValue` also controls the default values of each segment when the user first interacts with them, e.g. using the up and down arrow keys. By default, the `placeholderValue` is midnight.
9
:
AM
```
Copytsx
<TimeField placeholderValue={new Time(9)}>
<TimeField.Field>{segment => <TimeField.Segment segment={segment()} />}</TimeField.Field>
</TimeField>
```
```
Copytsx
<TimeField placeholderValue={new Time(9)}>
<TimeField.Field>{segment => <TimeField.Segment segment={segment()} />}</TimeField.Field>
</TimeField>
```
### Hide Time Zone
When a `ZonedDateTime` object is provided as the value of a TimeField, the time zone abbreviation is displayed by default. It can be hidden using the `hideTimeZone` prop.
12
:
45
AM
Selected Time: 12:45 AM
```
Copytsx
<TimeField defaultValue={parseZonedDateTime("2022-11-07T00:45[America/Los_Angeles]")} hideTimeZone>
<TimeField.Field>{segment => <TimeField.Segment segment={segment()} />}</TimeField.Field>
</TimeField>
```
```
Copytsx
<TimeField defaultValue={parseZonedDateTime("2022-11-07T00:45[America/Los_Angeles]")} hideTimeZone>
<TimeField.Field>{segment => <TimeField.Segment segment={segment()} />}</TimeField.Field>
</TimeField>
```
### Hour Cycle
By default, TimeField displays times in either 12 or 24 hour format depending on the user's locale. This can be overridden using the `hourCycle` prop.
:
```
Copytsx
<TimeField hourCycle={24}>
<TimeField.Field>{segment => <TimeField.Segment segment={segment()} />}</TimeField.Field>
</TimeField>
```
```
Copytsx
<TimeField hourCycle={24}>
<TimeField.Field>{segment => <TimeField.Segment segment={segment()} />}</TimeField.Field>
</TimeField>
```
### Description
The `TimeField.Description` component can be used to associate additional help text with a time field.
Time
:
AM
Select a meeting time.
```
Copytsx
<TimeField>
<TimeField.Label>Time</TimeField.Label>
<TimeField.Field>{segment => <TimeField.Segment segment={segment()} />}</TimeField.Field>
<TimeField.Description>Select a meeting time.</TimeField.Description>
</TimeField>
```
```
Copytsx
<TimeField>
<TimeField.Label>Time</TimeField.Label>
<TimeField.Field>{segment => <TimeField.Segment segment={segment()} />}</TimeField.Field>
<TimeField.Description>Select a meeting time.</TimeField.Description>
</TimeField>
```
### Error message
The `TimeField.ErrorMessage` component can be used to help the user fix a validation error. It should be combined with the `validationState` prop to semantically mark the time field as invalid for assistive technologies.
By default, it will render only when the `validationState` prop is set to `invalid`, use the `forceMount` prop to always render the error message (ex: for usage with animation libraries).
Time
:
AM
Please select a time.
```
Copytsx
import { createSignal } from "solid-js";
function ErrorMessageExample() {
const [value, setValue] = createSignal(undefined);
return (
<TimeField
value={value()}
onChange={setValue}
validationState={value() === undefined ? "invalid" : "valid"}
>
<TimeField.Label>Time</TimeField.Label>
<TimeField.Field>{segment => <TimeField.Segment segment={segment()} />}</TimeField.Field>
<TimeField.ErrorMessage>Please select a time.</TimeField.ErrorMessage>
</TimeField>
);
}
```
```
Copytsx
import { createSignal } from "solid-js";
function ErrorMessageExample() {
const [value, setValue] = createSignal(undefined);
return (
<TimeField
value={value()}
onChange={setValue}
validationState={value() === undefined ? "invalid" : "valid"}
>
<TimeField.Label>Time</TimeField.Label>
<TimeField.Field>{segment => <TimeField.Segment segment={segment()} />}</TimeField.Field>
<TimeField.ErrorMessage>Please select a time.</TimeField.ErrorMessage>
</TimeField>
);
}
```
### HTML forms
The `name` prop can be used for integration with HTML forms.
:
AM
ResetSubmit
```
Copytsx
function HTMLFormExample() {
const onSubmit = (e: SubmitEvent) => {
// handle form submission.
};
return (
<form onSubmit={onSubmit}>
<TimeField name="time">
<TimeField.Field>{segment => <TimeField.Segment segment={segment()} />}</TimeField.Field>
<TimeField.HiddenInput />
</TimeField>
<div>
<button type="reset">Reset</button>
<button type="submit">Submit</button>
</div>
</form>
);
}
```
```
Copytsx
function HTMLFormExample() {
const onSubmit = (e: SubmitEvent) => {
// handle form submission.
};
return (
<form onSubmit={onSubmit}>
<TimeField name="time">
<TimeField.Field>{segment => <TimeField.Segment segment={segment()} />}</TimeField.Field>
<TimeField.HiddenInput />
</TimeField>
<div>
<button type="reset">Reset</button>
<button type="submit">Submit</button>
</div>
</form>
);
}
```
## API Reference
### TimeField
`TimeField` is equivalent to the `Root` import from `@kobalte/core/time-field`.
| Prop | Description |
| --- | --- |
| value | `TimeValue`<br> The current value (controlled). |
| defaultValue | `TimeValue`<br> The default value (uncontrolled). |
| onChange | `(value: MappedTimeValue<TimeValue>) => void`<br> Handler that is called when the value changes. |
| hourCycle | `12 | 24`<br> Whether to display the time in 12 or 24-hour format. By default, this is determined by the user's locale. |
| granularity | `'hour' | 'minute' | 'second'`<br> Determines the smallest unit that is displayed in the time field. Defaults to `"minute"`. |
| hideTimeZone | `boolean`<br> Whether to hide the time zone abbreviation. |
| shouldForceLeadingZeros | `boolean`<br> Whether to always show leading zeros in the hour field. By default, this is determined by the user's locale. |
| placeholderValue | `TimeValue`<br> A placeholder time that influences the format of the placeholder shown when no value is selected. Defaults to 12:00 AM or 00:00 depending on the hour cycle. |
| minValue | `TimeValue`<br> The minimum allowed time that a user may select. |
| maxValue | `TimeValue`<br> The maximum allowed time that a user may select. |
| name | `string`<br> The name of the time field. Submitted with its owning form as part of a name/value pair. |
| validationState | `'valid' | 'invalid'`<br> Whether the time field should display its "valid" or "invalid" visual styling. |
| required | `boolean`<br> Whether the time field is required. |
| disabled | `boolean`<br> Whether the time field is disabled. |
| readOnly | `boolean`<br> Whether the time field is read only. |
| translations | `TimeFieldIntlTranslations`<br> The localized strings of the component. |
| Data attribute | Description |
| --- | --- |
| data-valid | Present when the time field is valid according to the validation rules. |
| data-invalid | Present when the time field is invalid according to the validation rules. |
| data-required | Present when the time field is required. |
| data-disabled | Present when the time field is disabled. |
| data-readonly | Present when the time field is read only. |
`TimeField.Label`, `TimeField.Field`, `TimeField.Segment`, `TimeField.Description` and `TimeField.ErrorMesssage` share the same data-attributes.
### TimeField.Segment
| Prop | Description |
| --- | --- |
| segment | `TimeSegment`<br> A segment of the time field. |
| Data attribute | Description |
| --- | --- |
| data-separator | Present when the segment is a separator. |
| data-type | Always present. |
| data-placeholder | Present when the segment's value is a placeholder. |
### TimeField.ErrorMessage
| Prop | Description |
| --- | --- |
| forceMount | `boolean`<br> Used to force mounting when more control is needed. Useful when controlling animation with SolidJS animation libraries. |
## Rendered elements
| Component | Default rendered element |
| --- | --- |
| `TimeField` | `div` |
| `TimeField.Label` | `span` |
| `TimeField.Field` | `div` |
| `TimeField.Segment` | `div` |
| `TimeField.Description` | `div` |
| `TimeField.ErrorMessage` | `div` |
| `TimeField.HiddenInput` | `input` |
## Accessibility
### Keyboard Interactions
| Key | Description |
| --- | --- |
| `Backspace` | Deletes the value in the current segment and moves focus to the previous segment when empty. |
| `Delete` | Deletes the value in the current segment and moves focus to the previous segment when empty. |
| `ArrowRight` | Moves focus to the next segment. |
| `ArrowLeft` | Moves focus to the previous segment. |
| `ArrowUp` | Increments the given segment. Upon reaching the minimum or maximum value, the value wraps around to the opposite limit. |
| `ArrowDown` | Decrements the given segment. Upon reaching the minimum or maximum value, the value wraps around to the opposite limit. |
| `PageUp` | Increments the given segment by a larger amount, rounding it to the nearest increment. The amount to increment by depends on the segment, for example 2 hours, 15 minutes, and 15 seconds. Upon reaching the minimum or maximum value, the value wraps around to the opposite limit. |
| `PageDown` | Decrements the given segment by a larger amount, rounding it to the nearest decrement. The amount to decrement by depends on the segment, for example 2 hours, 15 minutes, and 15 seconds. Upon reaching the minimum or maximum value, the value wraps around to the opposite limit. |
| `Home` | Decrements the given segment by the segment's minimum value. |
| `End` | Increments the given segment by the segment's maximum value. |
Previous[←Text Field](https://kobalte.dev/docs/core/components/text-field)Next[Toast→](https://kobalte.dev/docs/core/components/toast)

860
.firecrawl/kobalte-toast.md Normal file
View File

@ -0,0 +1,860 @@
# Toast
A succinct message that is displayed temporarily.
## Import
```
Copyts
import { Toast, toaster } from "@kobalte/core/toast";
// or
import { Root, toaster, ... } from "@kobalte/core/toast";
// or (deprecated)
import { Toast, toaster } from "@kobalte/core";
```
```
Copyts
import { Toast, toaster } from "@kobalte/core/toast";
// or
import { Root, toaster, ... } from "@kobalte/core/toast";
// or (deprecated)
import { Toast, toaster } from "@kobalte/core";
```
## Features
- Automatically closes.
- Pauses closing on hover, focus and window blur.
- Supports hotkey to jump to toast region.
- Supports closing via swipe gesture.
- Exposes CSS variables for swipe gesture animations.
- Limit the number of visible toasts.
- Manage promises within toast.
- Can remove or update toast programmatically.
- Multiple toast regions.
## Anatomy
The toast region consists of:
- **Toast.Region:** The fixed area where toasts appear. Users can jump to the viewport by pressing a hotkey.
- **Toast.List:** The list containing all rendered toasts.
```
Copytsx
<Toast.Region>
<Toast.List />
</Toast.Region>
```
```
Copytsx
<Toast.Region>
<Toast.List />
</Toast.Region>
```
The toast consists of:
- **Toast:** The root container for a toast.
- **Toast.CloseButton:** The button that closes the toast.
- **Toast.Title:** An accessible title to be announced when the toast is opened.
- **Toast.Description:** An optional accessible description to be announced when the toast is opened.
- **Toast.ProgressTrack:** The component that visually represents the lifetime of the toast.
- **Toast.ProgressFill:** The component that visually represents the remaining lifetime of the toast.
```
Copytsx
<Toast>
<Toast.CloseButton />
<Toast.Title />
<Toast.Description />
<Toast.ProgressTrack>
<Toast.ProgressFill />
</Toast.ProgressTrack>
</Toast>
```
```
Copytsx
<Toast>
<Toast.CloseButton />
<Toast.Title />
<Toast.Description />
<Toast.ProgressTrack>
<Toast.ProgressFill />
</Toast.ProgressTrack>
</Toast>
```
## Example
Show toastUpdate toast
index.tsxstyle.css
```
Copytsx
import { Toast, toaster } from "@kobalte/core/toast";
import { CrossIcon } from "some-icon-library";
import "./style.css";
function App() {
let id: number;
const showToast = () => {
id = toaster.show(props => (
<Toast toastId={props.toastId} class="toast">
<div class="toast__content">
<div>
<Toast.Title class="toast__title">Event has been created</Toast.Title>
<Toast.Description class="toast__description">
Monday, January 3rd at 6:00pm
</Toast.Description>
</div>
<Toast.CloseButton class="toast__close-button">
<CrossIcon />
</Toast.CloseButton>
</div>
<Toast.ProgressTrack class="toast__progress-track">
<Toast.ProgressFill class="toast__progress-fill" />
</Toast.ProgressTrack>
</Toast>
));
};
const updateToast = () => {
toaster.update(id, props => (
<Toast toastId={props.toastId} class="toast">
<div class="toast__content">
<div>
<Toast.Title class="toast__title">Event has been updated</Toast.Title>
<Toast.Description class="toast__description">
Friday, January 7th at 10:00pm
</Toast.Description>
</div>
<Toast.CloseButton class="toast__close-button">
<CrossIcon />
</Toast.CloseButton>
</div>
<Toast.ProgressTrack class="toast__progress-track">
<Toast.ProgressFill class="toast__progress-fill" />
</Toast.ProgressTrack>
</Toast>
));
};
return (
<>
<button onClick={showToast}>
Show toast
</button>
<button onClick={updateToast}>
Update toast
</button>
<Portal>
<Toast.Region>
<Toast.List class="toast__list" />
</Toast.Region>
</Portal>
</>
);
}
```
```
Copytsx
import { Toast, toaster } from "@kobalte/core/toast";
import { CrossIcon } from "some-icon-library";
import "./style.css";
function App() {
let id: number;
const showToast = () => {
id = toaster.show(props => (
<Toast toastId={props.toastId} class="toast">
<div class="toast__content">
<div>
<Toast.Title class="toast__title">Event has been created</Toast.Title>
<Toast.Description class="toast__description">
Monday, January 3rd at 6:00pm
</Toast.Description>
</div>
<Toast.CloseButton class="toast__close-button">
<CrossIcon />
</Toast.CloseButton>
</div>
<Toast.ProgressTrack class="toast__progress-track">
<Toast.ProgressFill class="toast__progress-fill" />
</Toast.ProgressTrack>
</Toast>
));
};
const updateToast = () => {
toaster.update(id, props => (
<Toast toastId={props.toastId} class="toast">
<div class="toast__content">
<div>
<Toast.Title class="toast__title">Event has been updated</Toast.Title>
<Toast.Description class="toast__description">
Friday, January 7th at 10:00pm
</Toast.Description>
</div>
<Toast.CloseButton class="toast__close-button">
<CrossIcon />
</Toast.CloseButton>
</div>
<Toast.ProgressTrack class="toast__progress-track">
<Toast.ProgressFill class="toast__progress-fill" />
</Toast.ProgressTrack>
</Toast>
));
};
return (
<>
<button onClick={showToast}>
Show toast
</button>
<button onClick={updateToast}>
Update toast
</button>
<Portal>
<Toast.Region>
<Toast.List class="toast__list" />
</Toast.Region>
</Portal>
</>
);
}
```
## Usage
### Showing a toast
To create a toast, use the `toaster.show()` method and pass the generated `toastId` to `Toast.Root`.
```
Copytsx
const id = toaster.show(props => <Toast toastId={props.toastId}>...</Toast>);
```
```
Copytsx
const id = toaster.show(props => <Toast toastId={props.toastId}>...</Toast>);
```
### Updating a toast
The `toaster.update()` method can be used to update a toast by providing a toast id and the new component to render.
```
Copytsx
toaster.update(id, props => <Toast toastId={props.toastId}>...</Toast>);
```
```
Copytsx
toaster.update(id, props => <Toast toastId={props.toastId}>...</Toast>);
```
Notice that you always need to pass the `toastId` to `Toast`, because it doesn't know anything
about the `toaster` nor the toast it is supposed to represent.
### Dismissing a toast
The `toaster.dismiss()` method can be used to dismiss a toast by providing a toast id.
```
Copytsx
toaster.dismiss(id);
```
```
Copytsx
toaster.dismiss(id);
```
### Clearing the toast stack and queue
Use `toaster.clear()` method to dismiss all toasts.
```
Copytsx
toaster.clear();
```
```
Copytsx
toaster.clear();
```
### Handling promises
The toaster API exposes a `toaster.promise()` method to allow you update a toast when the promise resolves or rejects.
In addition to the `toastId`, props will contain the following properties:
- **state**: The state of the promise, can be `"pending" | "fulfilled" | "rejected"`.
- **data**: The data returned by the promise when fulfilled, if any.
- **error**: The error returned by the promise when rejected, if any.
```
Copytsx
toaster.promise(promise, props => (
<Toast toastId={props.toastId}>
<Switch>
<Match when={props.state === "pending"}>Loading</Match>
<Match when={props.state === "fulfilled"}>{props.data}</Match>
<Match when={props.state === "rejected"}>{props.error}</Match>
</Switch>
</Toast>
));
```
```
Copytsx
toaster.promise(promise, props => (
<Toast toastId={props.toastId}>
<Switch>
<Match when={props.state === "pending"}>Loading</Match>
<Match when={props.state === "fulfilled"}>{props.data}</Match>
<Match when={props.state === "rejected"}>{props.error}</Match>
</Switch>
</Toast>
));
```
### Pausing the toasts
The `Toast.Region` component exposes the following props to pause the toasts it contains.
- `pauseOnInteraction`: prop can be used to pause the toasts close timeout when a toast is hovered or focused.
- `pauseOnPageIdle`: prop can be used to pause the toasts close timeout when the document loses focus or the page is idle (e.g. switching to a new browser tab).
```
Copytsx
<Toast.Region pauseOnPageIdle pauseOnInteraction>
<Toast.List />
</Toast.Region>
```
```
Copytsx
<Toast.Region pauseOnPageIdle pauseOnInteraction>
<Toast.List />
</Toast.Region>
```
### Limiting the number of visible toasts
Use the `limit` prop of the `Toast.Region` to limit the number of toasts visible at the same time. All toasts added after limit was reached will be added into queue and displayed when a visible toast is closed.
```
Copytsx
<Toast.Region limit={3}>
<Toast.List />
</Toast.Region>
```
```
Copytsx
<Toast.Region limit={3}>
<Toast.List />
</Toast.Region>
```
### Progress bar fill width
We expose a CSS custom property `--kb-toast-progress-fill-width` which corresponds to the remaining toast lifetime (in percentage). If you are building a linear progress bar to show the toast duration, you can use it to set the width of the `Toast.ProgressFill` component in CSS.
```
Copycss
/* style.css*/
.toast__progress-fill {
background-color: hsl(200 98% 39%);
border-radius: 3px;
height: 100%;
width: var(--kb-toast-progress-fill-width);
transition: width 250ms linear;
}
```
```
Copycss
/* style.css*/
.toast__progress-fill {
background-color: hsl(200 98% 39%);
border-radius: 3px;
height: 100%;
width: var(--kb-toast-progress-fill-width);
transition: width 250ms linear;
}
```
### Animating swipe gesture
We expose the CSS custom properties `--kb-toast-swipe-move-[x|y]` and `--kb-toast-swipe-end-[x|y]` which can be used with `data-swipe="[start|move|cancel|end]"` attributes to animate a swipe to close gesture.
```
Copytsx
// index.tsx
import { Toast, toaster } from "@kobalte/core/toast";
import "./style.css";
function App() {
const showToast = () => {
toaster.show(props => (
<Toast toastId={props.toastId} class="toast">
...
</Toast>
));
};
return (
<>
<button onClick={showToast}>Show toast</button>
<Portal>
<Toast.Region swipeDirection="right">
<Toast.List />
</Toast.Region>
</Portal>
</>
);
}
```
```
Copytsx
// index.tsx
import { Toast, toaster } from "@kobalte/core/toast";
import "./style.css";
function App() {
const showToast = () => {
toaster.show(props => (
<Toast toastId={props.toastId} class="toast">
...
</Toast>
));
};
return (
<>
<button onClick={showToast}>Show toast</button>
<Portal>
<Toast.Region swipeDirection="right">
<Toast.List />
</Toast.Region>
</Portal>
</>
);
}
```
```
Copycss
/* style.css */
.toast[data-swipe="move"] {
transform: translateX(var(--kb-toast-swipe-move-x));
}
.toast[data-swipe="cancel"] {
transform: translateX(0);
transition: transform 200ms ease-out;
}
.toast[data-swipe="end"] {
animation: slideRight 100ms ease-out;
}
@keyframes slideRight {
from {
transform: translateX(var(--kb-toast-swipe-end-x));
}
to {
transform: translateX(100%);
}
}
```
```
Copycss
/* style.css */
.toast[data-swipe="move"] {
transform: translateX(var(--kb-toast-swipe-move-x));
}
.toast[data-swipe="cancel"] {
transform: translateX(0);
transition: transform 200ms ease-out;
}
.toast[data-swipe="end"] {
animation: slideRight 100ms ease-out;
}
@keyframes slideRight {
from {
transform: translateX(var(--kb-toast-swipe-end-x));
}
to {
transform: translateX(100%);
}
}
```
### Abstracting the `toaster` API
It's common in toast libraries to have method for displaying different type of toast like success or error. This can be done in Kobalte by abstracting the toaster API like below.
```
Copytsx
// toast.tsx
import { Toast, toaster } from "@kobalte/core/toast";
import { JSX } from "solid-js/jsx-runtime";
import { Switch, Match } from "solid-js/web";
function show(message: string) {
return toaster.show(props => (
<Toast toastId={props.toastId} class="toast">
{message}
</Toast>
));
}
function success(message: string) {
return toaster.show(props => (
<Toast toastId={props.toastId} class="toast toast--success">
{message}
</Toast>
));
}
function error(message: string) {
return toaster.show(props => (
<Toast toastId={props.toastId} class="toast toast--error">
{message}
</Toast>
));
}
function promise<T, U>(
promise: Promise<T> | (() => Promise<T>),
options: {
loading?: JSX.Element;
success?: (data: T) => JSX.Element;
error?: (error: U) => JSX.Element;
},
) {
return toaster.promise(promise, props => (
<Toast
toastId={props.toastId}
classList={{
toast: true,
"toast-loading": props.state === "pending",
"toast-success": props.state === "fulfilled",
"toast-error": props.state === "rejected",
}}
>
<Switch>
<Match when={props.state === "pending"}>{options.loading}</Match>
<Match when={props.state === "fulfilled"}>{options.success?.(props.data)}</Match>
<Match when={props.state === "rejected"}>{options.error?.(props.error)}</Match>
</Switch>
</Toast>
));
}
function custom(jsx: () => JSX.Element) {
return toaster.show(props => <Toast toastId={props.toastId}>{jsx}</Toast>);
}
function dismiss(id: number) {
return toaster.dismiss(id);
}
export const toast = {
show,
success,
error,
promise,
custom,
dismiss,
};
```
```
Copytsx
// toast.tsx
import { Toast, toaster } from "@kobalte/core/toast";
import { JSX } from "solid-js/jsx-runtime";
import { Switch, Match } from "solid-js/web";
function show(message: string) {
return toaster.show(props => (
<Toast toastId={props.toastId} class="toast">
{message}
</Toast>
));
}
function success(message: string) {
return toaster.show(props => (
<Toast toastId={props.toastId} class="toast toast--success">
{message}
</Toast>
));
}
function error(message: string) {
return toaster.show(props => (
<Toast toastId={props.toastId} class="toast toast--error">
{message}
</Toast>
));
}
function promise<T, U>(
promise: Promise<T> | (() => Promise<T>),
options: {
loading?: JSX.Element;
success?: (data: T) => JSX.Element;
error?: (error: U) => JSX.Element;
},
) {
return toaster.promise(promise, props => (
<Toast
toastId={props.toastId}
classList={{
toast: true,
"toast-loading": props.state === "pending",
"toast-success": props.state === "fulfilled",
"toast-error": props.state === "rejected",
}}
>
<Switch>
<Match when={props.state === "pending"}>{options.loading}</Match>
<Match when={props.state === "fulfilled"}>{options.success?.(props.data)}</Match>
<Match when={props.state === "rejected"}>{options.error?.(props.error)}</Match>
</Switch>
</Toast>
));
}
function custom(jsx: () => JSX.Element) {
return toaster.show(props => <Toast toastId={props.toastId}>{jsx}</Toast>);
}
function dismiss(id: number) {
return toaster.dismiss(id);
}
export const toast = {
show,
success,
error,
promise,
custom,
dismiss,
};
```
Then inside your application, use your own toast API:
```
Copytsx
// App.tsx
import { toast } from "./toast";
function App() {
const showToast = () => {
toast.success("Event has been created");
};
return (
<>
<button onClick={showToast}>Show toast</button>
<Portal>
<Toast.Region>
<Toast.List />
</Toast.Region>
</Portal>
</>
);
}
```
```
Copytsx
// App.tsx
import { toast } from "./toast";
function App() {
const showToast = () => {
toast.success("Event has been created");
};
return (
<>
<button onClick={showToast}>Show toast</button>
<Portal>
<Toast.Region>
<Toast.List />
</Toast.Region>
</Portal>
</>
);
}
```
### Multiple regions
The `region` option in `toaster.show()` allows you to display toast in multiple regions at the same time. Not providing a region uses the default one.
```
Copytsx
toaster.show(props => <Toast toastId={props.toastId}>...</Toast>, {
region: "custom-region-id",
});
```
```
Copytsx
toaster.show(props => <Toast toastId={props.toastId}>...</Toast>, {
region: "custom-region-id",
});
```
Inside your application, use add your custom region:
```
Copytsx
<Portal>
{/* Default region */}
<Toast.Region>
<Toast.List />
</Toast.Region>
<Toast.Region regionId="custom-region-id">
<Toast.List />
</Toast.Region>
</Portal>
```
```
Copytsx
<Portal>
{/* Default region */}
<Toast.Region>
<Toast.List />
</Toast.Region>
<Toast.Region regionId="custom-region-id">
<Toast.List />
</Toast.Region>
</Portal>
```
Show toastShow toast (custom region)
## API reference
### toaster
| Method | Description |
| --- | --- |
| show | `(toastComponent: ToastComponent, options?: ShowToastOptions) => number`<br> Adds a new toast to the visible toasts or queue depending on current state, region and limit, and return the id of the created toast. |
| update | `(id: number, toastComponent: ToastComponent) => void`<br> Update the toast of the given id with a new rendered component. |
| promise | `(promise: Promise<T> | (() => Promise<T>), toastComponent: ToastPromiseComponent<T, U>, options?: ShowToastOptions) => number`<br> Adds a new promise-based toast to the visible toasts or queue depending on current state and limit, and return the id of the created toast. |
| dismiss | `(id: number) => void`<br> Removes toast with given id from visible toasts and queue. |
| clear | `() => void`<br> Removes all toasts from visible toasts and queue. |
### Toast.Region
| Prop | Description |
| --- | --- |
| aria-label | `string`<br>**default:** "Notifications ({hotkey})" <br> A label for the toast region to provide context for screen reader users when navigating page landmarks. Can contain a `{hotkey}` placeholder which will be replaced for you. |
| hotkey | `string[]`<br>**default:** alt + T <br> The keys to use as the keyboard shortcut that will move focus to the toast region. Use `event.code` value for each key from [keycode.info](https://www.toptal.com/developers/keycode). For meta keys, use `ctrlKey`, `shiftKey`, `altKey` and/or `metaKey`. |
| duration | `number`<br>**default:** 5000 <br> The time in milliseconds that should elapse before automatically closing each toast. |
| limit | `number`<br>**default:** 3 <br> The maximum amount of toasts that can be displayed at the same time. |
| swipeDirection | `"up" | "down" | "left" | "right"`<br>**default:** "right" <br> The direction of the pointer swipe that should close the toast. |
| swipeThreshold | `number`<br>**default:** 50 <br> The distance in pixels that the swipe gesture must travel before a close is triggered. |
| pauseOnInteraction | `boolean`<br>**default:** true <br> Whether the toasts close timeout should pause when a toast is hovered or focused. |
| pauseOnPageIdle | `boolean`<br>**default:** true <br> Whether the toasts close timeout should pause when the document loses focus or the page is idle (e.g. switching to a new browser tab). |
| topLayer | `boolean`<br>**default:** true <br> Whether the toast region is marked as a "top layer", so that it: <br> \- is not aria-hidden when opening an overlay. <br> \- allows focus even outside a containing focus scope. <br> \- doesnt dismiss overlays when clicking on it, even though it is outside. |
| regionId | `string`<br> The custom id of the region used for multiple regions. |
| translations | [`ToastRegionIntlTranslations`](https://github.com/kobaltedev/kobalte/blob/main/packages/core/src/toast/toast.intl.ts)<br> Localization strings. |
### Toast
`Toast` is equivalent to the `Root` import from `@kobalte/core/toast` (and deprecated `Toast.Root`).
| Prop | Description |
| --- | --- |
| toastId | `number`<br> The id of the toast provided by the `toaster`. |
| priority | `"high" | "low"`<br>**default:** "high" <br> Control the sensitivity of the toast for accessibility purposes. For toasts that are the result of a user action, choose `high`. Toasts generated from background tasks should use `low`. |
| duration | `number`<br> The time in milliseconds that should elapse before automatically closing the toast. This will override the value supplied to `Toast.Region`. |
| persistent | `boolean`<br> Whether the toast should ignore duration and disappear only by a user action. |
| onPause | `() => void`<br> Event handler called when the dismiss timer is paused. This occurs when the pointer is moved over the region or the region is focused. |
| onResume | `() => void`<br> Event handler called when the dismiss timer is resumed. This occurs when the pointer is moved away from the region or the region is blurred. |
| onSwipeStart | `(event: SwipeEvent) => void`<br> Event handler called when starting a swipe interaction. |
| onSwipeMove | `(event: SwipeEvent) => void`<br> Event handler called during a swipe interaction. |
| onSwipeCancel | `(event: SwipeEvent) => void`<br> Event handler called when a swipe interaction is cancelled. |
| onSwipeEnd | `(event: SwipeEvent) => void`<br> Event handler called at the end of a swipe interaction. |
| onEscapeKeyDown | `(event: KeyboardEvent) => void`<br> Event handler called when the escape key is down. It can be prevented by calling `event.preventDefault`. |
| translations | [`ToastIntlTranslations`](https://github.com/kobaltedev/kobalte/blob/main/packages/core/src/toast/toast.intl.ts)<br> Localization strings. |
| Data attribute | Description |
| --- | --- |
| data-opened | Present when the toast is open. |
| data-closed | Present when the toast disappear. |
| data-swipe | The state of the swipe, can be `"start" | "move" | "cancel" | "end"`. |
| data-swipe-direction | The direction of the pointer swipe that should close the toast. |
| CSS custom property | Description |
| --- | --- |
| --kb-toast-swipe-move-x | The offset position of the toast when horizontally swiping. |
| --kb-toast-swipe-move-y | The offset position of the toast when vertically swiping. |
| --kb-toast-swipe-end-x | The offset end position of the toast after horizontally swiping. |
| --kb-toast-swipe-end-y | The offset end position of the toast after vertically swiping. |
## Rendered elements
| Component | Default rendered element |
| --- | --- |
| `Toast` | `li` |
| `Toast.CloseButton` | `button` |
| `Toast.Title` | `div` |
| `Toast.Description` | `div` |
| `Toast.ProgressTrack` | `div` |
| `Toast.ProgressFill` | `div` |
| `Toast.Region` | `div` |
| `Toast.List` | `ol` |
## Accessibility
### Keyboard Interactions
| Key | Description |
| --- | --- |
| `Alt` \+ `T` | Focuses toasts region. |
| `Tab` | Moves focus to the next focusable element. |
| `Shift` \+ `Tab` | Moves focus to the previous focusable element. |
| `Space` | When focus is on a `Toast.CloseButton`, closes the toast. |
| `Enter` | When focus is on a `Toast.CloseButton`, closes the toast. |
| `Esc` | When focus is on a toast, closes the toast. |
Previous[←Time Field](https://kobalte.dev/docs/core/components/time-field)Next[Toggle Button→](https://kobalte.dev/docs/core/components/toggle-button)

View File

@ -0,0 +1,55 @@
# @kobalte/utils ![TypeScript icon, indicating that this package has built-in type declarations](https://static-production.npmjs.com/4a2a680dfcadf231172b78b1d3beb975.svg)
0.9.1  Public  Published 2 years ago
- [Readme](https://www.npmjs.com/package/@kobalte/utils?activeTab=readme)
- [Code Beta](https://www.npmjs.com/package/@kobalte/utils?activeTab=code)
- [7 Dependencies](https://www.npmjs.com/package/@kobalte/utils?activeTab=dependencies)
- [10 Dependents](https://www.npmjs.com/package/@kobalte/utils?activeTab=dependents)
- [16 Versions](https://www.npmjs.com/package/@kobalte/utils?activeTab=versions)
# @kobalte/utils
[Permalink: @kobalte/utils](https://www.npmjs.com/package/@kobalte/utils#kobalteutils)
Common utilities and types for Kobalte.
## Installation
[Permalink: Installation](https://www.npmjs.com/package/@kobalte/utils#installation)
```
npm install @kobalte/utils
# or
yarn add @kobalte/utils
# or
pnpm add @kobalte/utils
```
## Documentation
[Permalink: Documentation](https://www.npmjs.com/package/@kobalte/utils#documentation)
For full documentation, visit [kobalte.dev](https://kobalte.dev/).
## Changelog
[Permalink: Changelog](https://www.npmjs.com/package/@kobalte/utils#changelog)
All notable changes are described in the [CHANGELOG.md](https://github.com/kobaltedev/kobalte/blob/HEAD/CHANGELOG.md) file.
## Readme
### Keywords
- [solid](https://www.npmjs.com/search?q=keywords:solid)
- [solidjs](https://www.npmjs.com/search?q=keywords:solidjs)
- [ui](https://www.npmjs.com/search?q=keywords:ui)
- [library](https://www.npmjs.com/search?q=keywords:library)
- [design-system](https://www.npmjs.com/search?q=keywords:design-system)
- [components](https://www.npmjs.com/search?q=keywords:components)
- [headless](https://www.npmjs.com/search?q=keywords:headless)
- [unstyled](https://www.npmjs.com/search?q=keywords:unstyled)
- [aria](https://www.npmjs.com/search?q=keywords:aria)
Viewing @kobalte/utils version 0.9.1

View File

@ -0,0 +1,258 @@
# Alert Dialog
A modal dialog that interrupts the user's workflow to communicate an important message and acquire a response. Examples include action confirmation prompts and error message confirmations. The alertdialog role enables assistive technologies and browsers to distinguish alert dialogs from other dialogs so they have the option of giving alert dialogs special treatment, such as playing a system alert sound.
## Import
```
Copyts
import { AlertDialog } from "@kobalte/core/alert-dialog";
// or
import { Root, Trigger, ... } from "@kobalte/core/alert-dialog";
// or (deprecated)
import { AlertDialog } from "@kobalte/core";
```
```
Copyts
import { AlertDialog } from "@kobalte/core/alert-dialog";
// or
import { Root, Trigger, ... } from "@kobalte/core/alert-dialog";
// or (deprecated)
import { AlertDialog } from "@kobalte/core";
```
## Features
- Follow the [WAI ARIA Alert Dialog](https://www.w3.org/WAI/ARIA/apg/patterns/alertdialog/) design pattern.
- Supports modal and non-modal modes.
- Provides screen reader announcements via rendered title and description.
- Focus is trapped and scrolling is blocked while it is open.
- Pressing `Esc` closes the alert dialog.
- Can be controlled or uncontrolled.
## Anatomy
The alert dialog consists of:
- **AlertDialog:** Contains all the parts of a dialog.
- **AlertDialog.Trigger:** The button that opens the dialog.
- **AlertDialog.Portal:** Portals its children into the `body` when the dialog is open.
- **AlertDialog.Overlay:** The layer that covers the inert portion of the view when the dialog is open.
- **AlertDialog.Content:** Contains the content to be rendered when the dialog is open.
- **AlertDialog.CloseButton:** The button that closes the dialog.
- **AlertDialog.Title:** An accessible title to be announced when the dialog is opened.
- **AlertDialog.Description:** An optional accessible description to be announced when the dialog is opened.
```
Copytsx
<AlertDialog>
<AlertDialog.Trigger />
<AlertDialog.Portal>
<AlertDialog.Overlay />
<AlertDialog.Content>
<AlertDialog.CloseButton />
<AlertDialog.Title />
<AlertDialog.Description />
</AlertDialog.Content>
</AlertDialog.Portal>
</AlertDialog>
```
```
Copytsx
<AlertDialog>
<AlertDialog.Trigger />
<AlertDialog.Portal>
<AlertDialog.Overlay />
<AlertDialog.Content>
<AlertDialog.CloseButton />
<AlertDialog.Title />
<AlertDialog.Description />
</AlertDialog.Content>
</AlertDialog.Portal>
</AlertDialog>
```
## Example
Open
index.tsxstyle.css
```
Copytsx
import { AlertDialog } from "@kobalte/core/alert-dialog";
import { CrossIcon } from "some-icon-library";
import "./style.css";
function App() {
return (
<AlertDialog>
<AlertDialog.Trigger class="alert-dialog__trigger">Open</AlertDialog.Trigger>
<AlertDialog.Portal>
<AlertDialog.Overlay class="alert-dialog__overlay" />
<div class="alert-dialog__positioner">
<AlertDialog.Content class="alert-dialog__content">
<div class="alert-dialog__header">
<AlertDialog.Title class="alert-dialog__title">Alert Dialog</AlertDialog.Title>
<AlertDialog.CloseButton class="alert-dialog__close-button">
<CrossIcon />
</AlertDialog.CloseButton>
</div>
<AlertDialog.Description class="alert-dialog__description">
An Alert Dialog enables assistive technologies and browsers to distinguish alert dialogs from other dialogs so they have the option of giving alert dialogs special treatment, such as playing a system alert sound.
</AlertDialog.Description>
</AlertDialog.Content>
</div>
</AlertDialog.Portal>
</AlertDialog>
);
}
```
```
Copytsx
import { AlertDialog } from "@kobalte/core/alert-dialog";
import { CrossIcon } from "some-icon-library";
import "./style.css";
function App() {
return (
<AlertDialog>
<AlertDialog.Trigger class="alert-dialog__trigger">Open</AlertDialog.Trigger>
<AlertDialog.Portal>
<AlertDialog.Overlay class="alert-dialog__overlay" />
<div class="alert-dialog__positioner">
<AlertDialog.Content class="alert-dialog__content">
<div class="alert-dialog__header">
<AlertDialog.Title class="alert-dialog__title">Alert Dialog</AlertDialog.Title>
<AlertDialog.CloseButton class="alert-dialog__close-button">
<CrossIcon />
</AlertDialog.CloseButton>
</div>
<AlertDialog.Description class="alert-dialog__description">
An Alert Dialog enables assistive technologies and browsers to distinguish alert dialogs from other dialogs so they have the option of giving alert dialogs special treatment, such as playing a system alert sound.
</AlertDialog.Description>
</AlertDialog.Content>
</div>
</AlertDialog.Portal>
</AlertDialog>
);
}
```
## Usage
### Default open
An initial, uncontrolled open value can be provided using the `defaultOpen` prop.
```
Copytsx
<AlertDialog defaultOpen>...</AlertDialog>
```
```
Copytsx
<AlertDialog defaultOpen>...</AlertDialog>
```
### Controlled open
The `open` prop can be used to make the open state controlled. The `onOpenChange` event is fired when the user presses the trigger, close button or overlay, and receives the new value.
```
Copytsx
import { createSignal } from "solid-js";
function ControlledExample() {
const [open, setOpen] = createSignal(false);
return (
<AlertDialog open={open()} onOpenChange={setOpen}>
...
</AlertDialog>
);
}
```
```
Copytsx
import { createSignal } from "solid-js";
function ControlledExample() {
const [open, setOpen] = createSignal(false);
return (
<AlertDialog open={open()} onOpenChange={setOpen}>
...
</AlertDialog>
);
}
```
## API Reference
### AlertDialog
`AlertDialog` is equivalent to the `Root` import from `@kobalte/core/alert-dialog` (and deprecated `AlertDialog.Root`).
| Prop | Description |
| --- | --- |
| open | `boolean`<br> The controlled open state of the dialog. |
| defaultOpen | `boolean`<br> The default open state when initially rendered. Useful when you do not need to control the open state. |
| onOpenChange | `(open: boolean) => void`<br> Event handler called when the open state of the dialog changes. |
| id | `string`<br> A unique identifier for the component. The id is used to generate id attributes for nested components. If no id prop is provided, a generated id will be used. |
| modal | `boolean`<br> Whether the dialog should be the only visible content for screen readers, when set to `true`: <br> \- interaction with outside elements will be disabled. <br> \- scroll will be locked. <br> \- focus will be locked inside the dialog content. <br> \- elements outside the dialog content will not be visible for screen readers. |
| preventScroll | `boolean`<br> Whether the scroll should be locked even if the alert dialog is not modal. |
| forceMount | `boolean`<br> Used to force mounting the dialog (portal, overlay and content) when more control is needed. Useful when controlling animation with SolidJS animation libraries. |
### AlertDialog.Trigger
`AlertDialog.Trigger` consists of [Button](https://kobalte.dev/docs/core/components/button).
| Data attribute | Description |
| --- | --- |
| data-expanded | Present when the dialog is open. |
| data-closed | Present when the dialog is close. |
`AlertDialog.Content` and `AlertDialog.Overlay` shares the same data-attributes.
### AlertDialog.Content
| Prop | Description |
| --- | --- |
| onOpenAutoFocus | `(event: Event) => void`<br> Event handler called when focus moves into the component after opening. It can be prevented by calling `event.preventDefault`. |
| onCloseAutoFocus | `(event: Event) => void`<br> Event handler called when focus moves to the trigger after closing. It can be prevented by calling `event.preventDefault`. |
| onEscapeKeyDown | `(event: KeyboardEvent) => void`<br> Event handler called when the escape key is down. It can be prevented by calling `event.preventDefault`. |
| onPointerDownOutside | `(event: PointerDownOutsideEvent) => void`<br> Event handler called when a pointer event occurs outside the bounds of the component. It can be prevented by calling `event.preventDefault`. |
| onFocusOutside | `(event: FocusOutsideEvent) => void`<br> Event handler called when the focus moves outside the bounds of the component. It can be prevented by calling `event.preventDefault`. |
| onInteractOutside | `(event: InteractOutsideEvent) => void`<br> Event handler called when an interaction (pointer or focus event) happens outside the bounds of the component. It can be prevented by calling `event.preventDefault`. |
## Rendered elements
| Component | Default rendered element |
| --- | --- |
| `AlertDialog` | none |
| `AlertDialog.Trigger` | `button` |
| `AlertDialog.Portal` | `Portal` |
| `AlertDialog.Overlay` | `div` |
| `AlertDialog.Content` | `div` |
| `AlertDialog.CloseButton` | `button` |
| `AlertDialog.Title` | `h2` |
| `AlertDialog.Description` | `p` |
## Accessibility
### Keyboard Interactions
| Key | Description |
| --- | --- |
| `Space` | When focus is on the trigger, opens/closes the dialog. |
| `Enter` | When focus is on the trigger, opens/closes the dialog. |
| `Tab` | Moves focus to the next focusable element. |
| `Shift` \+ `Tab` | Moves focus to the previous focusable element. |
| `Esc` | Closes the dialog and moves focus to the trigger. |
Previous[←Alert](https://kobalte.dev/docs/core/components/alert)Next[Badge→](https://kobalte.dev/docs/core/components/badge)

View File

@ -0,0 +1,85 @@
# Alert
Display a brief, important message in a way that attracts the user's attention without interrupting the user's task.
## Import
```
Copyts
import { Alert } from "@kobalte/core/alert";
// or
import { Root } from "@kobalte/core/alert";
// or (deprecated)
import { Alert } from "@kobalte/core";
```
```
Copyts
import { Alert } from "@kobalte/core/alert";
// or
import { Root } from "@kobalte/core/alert";
// or (deprecated)
import { Alert } from "@kobalte/core";
```
## Features
- Adhere to the [WAI ARIA Alert](https://www.w3.org/WAI/ARIA/apg/patterns/alert/) design pattern.
## Anatomy
The alert consists of :
- **Alert:** The root container for an alert.
```
Copytsx
<Alert />
```
```
Copytsx
<Alert />
```
## Example
Kobalte is going live soon, get ready!
index.tsxstyle.css
```
Copytsx
import { Alert } from "@kobalte/core/alert";
import "./style.css";
function App() {
return <Alert class="alert">Kobalte is going live soon, get ready!</Alert>;
}
```
```
Copytsx
import { Alert } from "@kobalte/core/alert";
import "./style.css";
function App() {
return <Alert class="alert">Kobalte is going live soon, get ready!</Alert>;
}
```
## API reference
### Alert
`Alert` is equivalent to the `Root` import from `@kobalte/core/alert` (and deprecated `Alert.Root`).
Renders a `div` by default and support all its props.
## Rendered elements
| Component | Default rendered element |
| --- | --- |
| `Alert` | `div` |
Previous[←Accordion](https://kobalte.dev/docs/core/components/accordion)Next[Alert Dialog→](https://kobalte.dev/docs/core/components/alert-dialog)

View File

@ -0,0 +1,92 @@
# Badge
A `Badge` component is used to display small pieces of information or status indicators.
## Import
```
Copyts
import { Badge } from "@kobalte/core/badge";
// or
import { Root } from "@kobalte/core/badge";
```
```
Copyts
import { Badge } from "@kobalte/core/badge";
// or
import { Root } from "@kobalte/core/badge";
```
## Features
- Auto-populated ARIA labeling via the textValue prop for enhanced accessibility.
- Built-in ARIA support with role="status" to communicate dynamic updates.
## Anatomy
The badge consists of:
- **Badge:** The root container for the badge that supports accessibility and content customization.
```
Copytsx
<Badge />
```
```
Copytsx
<Badge />
```
## Example
5 messages
index.tsxstyle.css
```
Copytsx
import { Badge } from "@kobalte/core/badge";
import "./style.css";
function App() {
return (
<Badge class="badge" textValue="5 unread messages">
5 messages
</Badge>
);
}
```
```
Copytsx
import { Badge } from "@kobalte/core/badge";
import "./style.css";
function App() {
return (
<Badge class="badge" textValue="5 unread messages">
5 messages
</Badge>
);
}
```
## API Reference
### Badge
`Badge` is equivalent to the `Root` import from `@kobalte/core/badge`.
| Prop | Description |
| --- | --- |
| textValue | `string | undefined`<br> Accessible text description of the badge if child is not text |
## Rendered elements
| Component | Default rendered element |
| --- | --- |
| `Badge` | `span` |
Previous[←Alert Dialog](https://kobalte.dev/docs/core/components/alert-dialog)Next[Breadcrumbs→](https://kobalte.dev/docs/core/components/breadcrumbs)

View File

@ -0,0 +1,233 @@
Show hierarchy and navigational context for a users location within an application.
## Import
```
Copyts
import { Breadcrumbs } from "@kobalte/core/breadcrumbs";
// or
import { Root, Link, ... } from "@kobalte/core/breadcrumbs";
// or (deprecated)
import { Breadcrumbs } from "@kobalte/core";
```
```
Copyts
import { Breadcrumbs } from "@kobalte/core/breadcrumbs";
// or
import { Root, Link, ... } from "@kobalte/core/breadcrumbs";
// or (deprecated)
import { Breadcrumbs } from "@kobalte/core";
```
## Features
- Support for navigation links via `<a>` elements or custom element types via ARIA.
- Localized ARIA labeling support for landmark navigation region.
- Support for disabled breadcrumb links.
## Anatomy
The breadcrumbs consist of:
- **Breadcrumbs:** The root container for a breadcrumbs.
- **Breadcrumbs.Link:** The breadcrumb link.
- **Breadcrumbs.Separator:** The visual separator between each breadcrumb items. It will not be visible by screen readers.
- **ol:** The native HTML `<ol>` element used to contain breadcrumb items.
- **li:** The native HTML `<li>` element used to contain breadcrumb link and separator.
```
Copytsx
<Breadcrumbs>
<ol>
<li>
<Breadcrumbs.Link />
<Breadcrumbs.Separator />
</li>
</ol>
</Breadcrumbs>
```
```
Copytsx
<Breadcrumbs>
<ol>
<li>
<Breadcrumbs.Link />
<Breadcrumbs.Separator />
</li>
</ol>
</Breadcrumbs>
```
## Example
index.tsxstyle.css
```
Copytsx
import { Breadcrumbs } from "@kobalte/core/breadcrumbs";
import "./style.css";
function App() {
return (
<Breadcrumbs>
<ol class="breadcrumbs__list">
<li class="breadcrumbs__item">
<Breadcrumbs.Link href="/" class="breadcrumbs__link">
Home
</Breadcrumbs.Link>
<Breadcrumbs.Separator class="breadcrumbs__separator" />
</li>
<li class="breadcrumbs__item">
<Breadcrumbs.Link href="/components" class="breadcrumbs__link">
Components
</Breadcrumbs.Link>
<Breadcrumbs.Separator class="breadcrumbs__separator" />
</li>
<li class="breadcrumbs__item">
<Breadcrumbs.Link current class="breadcrumbs__link">
Breadcrumbs
</Breadcrumbs.Link>
</li>
</ol>
</Breadcrumbs>
);
}
```
```
Copytsx
import { Breadcrumbs } from "@kobalte/core/breadcrumbs";
import "./style.css";
function App() {
return (
<Breadcrumbs>
<ol class="breadcrumbs__list">
<li class="breadcrumbs__item">
<Breadcrumbs.Link href="/" class="breadcrumbs__link">
Home
</Breadcrumbs.Link>
<Breadcrumbs.Separator class="breadcrumbs__separator" />
</li>
<li class="breadcrumbs__item">
<Breadcrumbs.Link href="/components" class="breadcrumbs__link">
Components
</Breadcrumbs.Link>
<Breadcrumbs.Separator class="breadcrumbs__separator" />
</li>
<li class="breadcrumbs__item">
<Breadcrumbs.Link current class="breadcrumbs__link">
Breadcrumbs
</Breadcrumbs.Link>
</li>
</ol>
</Breadcrumbs>
);
}
```
## Usage
### Custom separator
Use the `separator` prop to provide a default content for all `Breadcrumbs.Separator`. You can pass it a `string` or a SolidJS component.
```
Copytsx
import { ChevronRightIcon } from "some-icon-library";
function App() {
return (
<Breadcrumbs separator={<ChevronRightIcon />}>
<ol class="breadcrumbs__list">
<li class="breadcrumbs__item">
<Breadcrumbs.Link href="/" class="breadcrumbs__link">
Home
</Breadcrumbs.Link>
<Breadcrumbs.Separator class="breadcrumbs__separator" />
</li>
<li class="breadcrumbs__item">
<Breadcrumbs.Link href="/components" class="breadcrumbs__link">
Components
</Breadcrumbs.Link>
<Breadcrumbs.Separator class="breadcrumbs__separator" />
</li>
<li class="breadcrumbs__item">
<Breadcrumbs.Link current class="breadcrumbs__link">
Breadcrumbs
</Breadcrumbs.Link>
</li>
</ol>
</Breadcrumbs>
);
}
```
```
Copytsx
import { ChevronRightIcon } from "some-icon-library";
function App() {
return (
<Breadcrumbs separator={<ChevronRightIcon />}>
<ol class="breadcrumbs__list">
<li class="breadcrumbs__item">
<Breadcrumbs.Link href="/" class="breadcrumbs__link">
Home
</Breadcrumbs.Link>
<Breadcrumbs.Separator class="breadcrumbs__separator" />
</li>
<li class="breadcrumbs__item">
<Breadcrumbs.Link href="/components" class="breadcrumbs__link">
Components
</Breadcrumbs.Link>
<Breadcrumbs.Separator class="breadcrumbs__separator" />
</li>
<li class="breadcrumbs__item">
<Breadcrumbs.Link current class="breadcrumbs__link">
Breadcrumbs
</Breadcrumbs.Link>
</li>
</ol>
</Breadcrumbs>
);
}
```
You can also override each `Breadcrumbs.Separator` content by providing your own `children`.
## API Reference
### Breadcrumbs
`Breadcrumbs` is equivalent to the `Root` import from `@kobalte/core/breadcrumbs` (and deprecated `Breadcrumbs.Root`).
| Prop | Description |
| --- | --- |
| separator | `string | JSX.Element`<br> The visual separator between each breadcrumb item. It will be used as the default children of `Breadcrumbs.Separator`. |
| translations | [`BreadcrumbsIntlTranslations`](https://github.com/kobaltedev/kobalte/blob/main/packages/core/src/breadcrumbs/breadcrumbs.intl.ts)<br> Localization strings. |
### Breadcrumbs.Link
`Breadcrumbs.Link` consists of [Link](https://kobalte.dev/docs/core/components/link).
| Prop | Description |
| --- | --- |
| current | `boolean`<br> Whether the breadcrumb link represents the current page. |
| disabled | `boolean`<br> Whether the breadcrumb link is disabled. |
| Data attribute | Description |
| --- | --- |
| data-current | Present when the breadcrumb link represents the current page. |
## Rendered elements
| Component | Default rendered element |
| --- | --- |
| `Breadcrumbs` | `nav` |
| `Breadcrumbs.Link` | `a` |
| `Breadcrumbs.Separator` | `span` |
Previous[←Badge](https://kobalte.dev/docs/core/components/badge)Next[Button→](https://kobalte.dev/docs/core/components/button)

View File

@ -0,0 +1,102 @@
# Button
Enables users to trigger an action or event, such as submitting a form, opening a dialog, canceling an action, or performing a delete operation.
## Import
```
Copyts
import { Button } from "@kobalte/core/button";
// or
import { Root } from "@kobalte/core/button";
// or (deprecated)
import { Button } from "@kobalte/core";
```
```
Copyts
import { Button } from "@kobalte/core/button";
// or
import { Root } from "@kobalte/core/button";
// or (deprecated)
import { Button } from "@kobalte/core";
```
## Features
- Native HTML `<button>` element support.
- `<a>` and custom element type support via the [WAI ARIA Button](https://www.w3.org/WAI/ARIA/apg/patterns/button/) design pattern.
- Keyboard event support for `Space` and `Enter` keys.
## Anatomy
The button consists of:
- **Button:** The root container for a button.
```
Copytsx
<Button />
```
```
Copytsx
<Button />
```
## Example
Click me
index.tsxstyle.css
```
Copytsx
import { Button } from "@kobalte/core/button";
import "./style.css";
function App() {
return <Button class="button">Click me</Button>;
}
```
```
Copytsx
import { Button } from "@kobalte/core/button";
import "./style.css";
function App() {
return <Button class="button">Click me</Button>;
}
```
## API Reference
### Button
`Button` is equivalent to the `Root` import from `@kobalte/core/button` (and deprecated `Button.Root`).
| Prop | Description |
| --- | --- |
| disabled | `boolean`<br> Whether the button is disabled. |
| Data attribute | Description |
| --- | --- |
| data-disabled | Present when the button is disabled. |
## Rendered elements
| Component | Default rendered element |
| --- | --- |
| `Button` | `button` |
## Accessibility
### Keyboard Interactions
| Key | Description |
| --- | --- |
| `Space` | Activates the button. |
| `Enter` | Activates the button. |
Previous[←Breadcrumbs](https://kobalte.dev/docs/core/components/breadcrumbs)Next[Checkbox→](https://kobalte.dev/docs/core/components/checkbox)

View File

@ -0,0 +1,400 @@
# Checkbox
A control that allows the user to toggle between checked and not checked.
## Import
```
Copyts
import { Checkbox } from "@kobalte/core/checkbox";
// or
import { Root, Input, ... } from "@kobalte/core/checkbox";
// or (deprecated)
import { Checkbox } from "@kobalte/core";
```
```
Copyts
import { Checkbox } from "@kobalte/core/checkbox";
// or
import { Root, Input, ... } from "@kobalte/core/checkbox";
// or (deprecated)
import { Checkbox } from "@kobalte/core";
```
## Features
- Built with a native HTML `<input>` element, which is visually hidden to allow custom styling.
- Syncs with form reset events.
- Labeling support for assistive technology.
- Support for description and error message help text linked to the input via ARIA.
- Can be controlled or uncontrolled.
## Anatomy
The checkbox consists of:
- **Checkbox:** The root container for a checkbox.
- **Checkbox.Input:** The native html input that is visually hidden in the checkbox.
- **Checkbox.Control:** The element that visually represents a checkbox.
- **Checkbox.Indicator:** The visual indicator rendered when the checkbox is in a checked or indeterminate state.
- **Checkbox.Label:** The label that gives the user information on the checkbox.
- **Checkbox.Description**: The description that gives the user more information on the checkbox.
- **Checkbox.ErrorMessage**: The error message that gives the user information about how to fix a validation error on the checkbox.
```
Copytsx
<Checkbox>
<Checkbox.Input />
<Checkbox.Control>
<Checkbox.Indicator />
</Checkbox.Control>
<Checkbox.Label />
<Checkbox.Description />
<Checkbox.ErrorMessage />
</Checkbox>
```
```
Copytsx
<Checkbox>
<Checkbox.Input />
<Checkbox.Control>
<Checkbox.Indicator />
</Checkbox.Control>
<Checkbox.Label />
<Checkbox.Description />
<Checkbox.ErrorMessage />
</Checkbox>
```
## Example
Subscribe
index.tsxstyle.css
```
Copytsx
import { Checkbox } from "@kobalte/core/checkbox";
import { CheckIcon } from "some-icon-library";
import "./style.css";
function App() {
return (
<Checkbox class="checkbox">
<Checkbox.Input class="checkbox__input" />
<Checkbox.Control class="checkbox__control">
<Checkbox.Indicator>
<CheckIcon />
</Checkbox.Indicator>
</Checkbox.Control>
<Checkbox.Label class="checkbox__label">Subscribe</Checkbox.Label>
</Checkbox>
);
}
```
```
Copytsx
import { Checkbox } from "@kobalte/core/checkbox";
import { CheckIcon } from "some-icon-library";
import "./style.css";
function App() {
return (
<Checkbox class="checkbox">
<Checkbox.Input class="checkbox__input" />
<Checkbox.Control class="checkbox__control">
<Checkbox.Indicator>
<CheckIcon />
</Checkbox.Indicator>
</Checkbox.Control>
<Checkbox.Label class="checkbox__label">Subscribe</Checkbox.Label>
</Checkbox>
);
}
```
## Usage
### Default checked
An initial, uncontrolled value can be provided using the `defaultChecked` prop.
Check
Subscribe
```
Copytsx
<Checkbox defaultChecked>...</Checkbox>
```
```
Copytsx
<Checkbox defaultChecked>...</Checkbox>
```
### Controlled checked
The `checked` prop can be used to make the checked state controlled. The `onChange` event is fired when the user presses the checkbox, and receives the new value.
Subscribe
You are unsubscribed.
```
Copytsx
import { createSignal } from "solid-js";
function ControlledExample() {
const [checked, setChecked] = createSignal(false);
return (
<>
<Checkbox checked={checked()} onChange={setChecked}>
...
</Checkbox>
<p>You are {checked() ? "subscribed" : "unsubscribed"}.</p>
</>
);
}
```
```
Copytsx
import { createSignal } from "solid-js";
function ControlledExample() {
const [checked, setChecked] = createSignal(false);
return (
<>
<Checkbox checked={checked()} onChange={setChecked}>
...
</Checkbox>
<p>You are {checked() ? "subscribed" : "unsubscribed"}.</p>
</>
);
}
```
### Description
The `Checkbox.Description` component can be used to associate additional help text with a checkbox.
Subscribe
You will receive our weekly newsletter.
```
Copytsx
<Checkbox>
<Checkbox.Input />
<Checkbox.Control>
<Checkbox.Indicator>
<CheckIcon />
</Checkbox.Indicator>
</Checkbox.Control>
<Checkbox.Label>Subscribe</Checkbox.Label>
<Checkbox.Description>You will receive our weekly newsletter.</Checkbox.Description>
</Checkbox>
```
```
Copytsx
<Checkbox>
<Checkbox.Input />
<Checkbox.Control>
<Checkbox.Indicator>
<CheckIcon />
</Checkbox.Indicator>
</Checkbox.Control>
<Checkbox.Label>Subscribe</Checkbox.Label>
<Checkbox.Description>You will receive our weekly newsletter.</Checkbox.Description>
</Checkbox>
```
### Error message
The `Checkbox.ErrorMessage` component can be used to help the user fix a validation error. It should be combined with the `validationState` prop to semantically mark the checkbox as invalid for assistive technologies.
By default, it will render only when the `validationState` prop is set to `invalid`, use the `forceMount` prop to always render the error message (ex: for usage with animation libraries).
Subscribe
You must agree to our Terms and Conditions.
```
Copytsx
import { createSignal } from "solid-js";
function ErrorMessageExample() {
const [checked, setChecked] = createSignal(false);
return (
<Checkbox
checked={checked()}
onChange={setChecked}
validationState={!checked() ? "invalid" : "valid"}
>
<Checkbox.Input />
<Checkbox.Control>
<Checkbox.Indicator>
<CheckIcon />
</Checkbox.Indicator>
</Checkbox.Control>
<Checkbox.Label>Agree</Checkbox.Label>
<Checkbox.ErrorMessage>You must agree to our Terms and Conditions.</Checkbox.ErrorMessage>
</Checkbox>
);
}
```
```
Copytsx
import { createSignal } from "solid-js";
function ErrorMessageExample() {
const [checked, setChecked] = createSignal(false);
return (
<Checkbox
checked={checked()}
onChange={setChecked}
validationState={!checked() ? "invalid" : "valid"}
>
<Checkbox.Input />
<Checkbox.Control>
<Checkbox.Indicator>
<CheckIcon />
</Checkbox.Indicator>
</Checkbox.Control>
<Checkbox.Label>Agree</Checkbox.Label>
<Checkbox.ErrorMessage>You must agree to our Terms and Conditions.</Checkbox.ErrorMessage>
</Checkbox>
);
}
```
### HTML forms
The `name` and `value` props can be used for integration with HTML forms.
Subscribe
ResetSubmit
```
Copytsx
function HTMLFormExample() {
const onSubmit = (e: SubmitEvent) => {
// handle form submission.
};
return (
<form onSubmit={onSubmit}>
<Checkbox name="newsletter" value="subscribe">
...
</Checkbox>
<div>
<button type="reset">Reset</button>
<button type="submit">Submit</button>
</div>
</form>
);
}
```
```
Copytsx
function HTMLFormExample() {
const onSubmit = (e: SubmitEvent) => {
// handle form submission.
};
return (
<form onSubmit={onSubmit}>
<Checkbox name="newsletter" value="subscribe">
...
</Checkbox>
<div>
<button type="reset">Reset</button>
<button type="submit">Submit</button>
</div>
</form>
);
}
```
## API Reference
### Checkbox
`Checkbox` is equivalent to the `Root` import from `@kobalte/core/checkbox` (and deprecated `Checkbox.Root`).
| Prop | Description |
| --- | --- |
| checked | `boolean`<br> The controlled checked state of the checkbox. |
| defaultChecked | `boolean`<br> The default checked state when initially rendered. Useful when you do not need to control the checked state. |
| onChange | `(checked: boolean) => void`<br> Event handler called when the checked state of the checkbox changes. |
| indeterminate | `boolean`<br> Whether the checkbox is in an indeterminate state. |
| name | `string`<br> The name of the checkbox, used when submitting an HTML form. See [MDN](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#htmlattrdefname). |
| value | `string`<br> The value of the checkbox, used when submitting an HTML form. See [MDN](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#htmlattrdefvalue). |
| validationState | `'valid' | 'invalid'`<br> Whether the checkbox should display its "valid" or "invalid" visual styling. |
| required | `boolean`<br> Whether the user must check the checkbox before the owning form can be submitted. |
| disabled | `boolean`<br> Whether the checkbox is disabled. |
| readOnly | `boolean`<br> Whether the checkbox can be checked but not changed by the user. |
| children | `JSX.Element | (state: CheckboxState) => JSX.Element`<br> The children of the checkbox. Can be a `JSX.Element` or a _render prop_ for having access to the internal state. |
| Render Prop | Description |
| --- | --- |
| checked | `Accessor<boolean>`<br> Whether the checkbox is checked or not. |
| indeterminate | `Accessor<boolean>`<br> Whether the checkbox is in an indeterminate state. |
| Data attribute | Description |
| --- | --- |
| data-valid | Present when the checkbox is valid according to the validation rules. |
| data-invalid | Present when the checkbox is invalid according to the validation rules. |
| data-required | Present when the checkbox is required. |
| data-disabled | Present when the checkbox is disabled. |
| data-readonly | Present when the checkbox is read only. |
| data-checked | Present when the checkbox is checked. |
| data-indeterminate | Present when the checkbox is in an indeterminate state. |
`Checkbox.Input`, `Checkbox.Control`, `Checkbox.Indicator`, `Checkbox.Label`, `Checkbox.Description` and `Checkbox.ErrorMessage` share the same data-attributes.
### Checkbox.Indicator
| Prop | Description |
| --- | --- |
| forceMount | `boolean`<br> Used to force mounting when more control is needed. Useful when controlling animation with SolidJS animation libraries. |
### Checkbox.ErrorMessage
| Prop | Description |
| --- | --- |
| forceMount | `boolean`<br> Used to force mounting when more control is needed. Useful when controlling animation with SolidJS animation libraries. |
## Rendered elements
| Component | Default rendered element |
| --- | --- |
| `Checkbox` | `div` |
| `Checkbox.Input` | `input` |
| `Checkbox.Control` | `div` |
| `Checkbox.Indicator` | `div` |
| `Checkbox.Label` | `label` |
| `Checkbox.Description` | `div` |
| `Checkbox.ErrorMessage` | `div` |
## Accessibility
### Keyboard Interactions
| Key | Description |
| --- | --- |
| `Space` | Toggles the checkbox on and off. |
Previous[←Button](https://kobalte.dev/docs/core/components/button)Next[Collapsible→](https://kobalte.dev/docs/core/components/collapsible)

View File

@ -0,0 +1,217 @@
# Collapsible
An interactive component which expands/collapses a content.
## Import
```
Copyts
import { Collapsible } from "@kobalte/core/collapsible";
// or
import { Root, Trigger, ... } from "@kobalte/core/collapsible";
// or (deprecated)
import { Collapsible } from "@kobalte/core";
```
```
Copyts
import { Collapsible } from "@kobalte/core/collapsible";
// or
import { Root, Trigger, ... } from "@kobalte/core/collapsible";
// or (deprecated)
import { Collapsible } from "@kobalte/core";
```
## Features
- Follow the [WAI ARIA Disclosure](https://www.w3.org/WAI/ARIA/apg/patterns/disclosure/) design pattern.
- Can be controlled or uncontrolled.
## Anatomy
The collapsible consists of:
- **Collapsible:** The root container for a collapsible.
- **Collapsible.Trigger:** The button that expands/collapses the collapsible content.
- **Collapsible.Content:** Contains the content to be rendered when the collapsible is expanded.
```
Copytsx
<Collapsible>
<Collapsible.Trigger />
<Collapsible.Content />
</Collapsible>
```
```
Copytsx
<Collapsible>
<Collapsible.Trigger />
<Collapsible.Content />
</Collapsible>
```
## Example
What is Kobalte?Chevron
index.tsxstyle.css
```
Copytsx
import { Collapsible } from "@kobalte/core/collapsible";
import { ChevronDownIcon } from "some-icon-library";
import "./style.css";
function App() {
return (
<Collapsible class="collapsible">
<Collapsible.Trigger class="collapsible__trigger">
<span>What is Kobalte?</span>
<ChevronDownIcon class="collapsible__trigger-icon" />
</Collapsible.Trigger>
<Collapsible.Content class="collapsible__content">
<p class="collapsible__content-text">
Kobalte is a UI toolkit for building accessible web apps and design systems with SolidJS.
It provides a set of low-level UI components and primitives which can be the foundation
for your design system implementation.
</p>
</Collapsible.Content>
</Collapsible>
);
}
```
```
Copytsx
import { Collapsible } from "@kobalte/core/collapsible";
import { ChevronDownIcon } from "some-icon-library";
import "./style.css";
function App() {
return (
<Collapsible class="collapsible">
<Collapsible.Trigger class="collapsible__trigger">
<span>What is Kobalte?</span>
<ChevronDownIcon class="collapsible__trigger-icon" />
</Collapsible.Trigger>
<Collapsible.Content class="collapsible__content">
<p class="collapsible__content-text">
Kobalte is a UI toolkit for building accessible web apps and design systems with SolidJS.
It provides a set of low-level UI components and primitives which can be the foundation
for your design system implementation.
</p>
</Collapsible.Content>
</Collapsible>
);
}
```
## Usage
### Animating content size
We expose the CSS custom properties `--kb-collapsible-content-width` and `--kb-collapsible-content-height` which you can use to animate the size of the content when it opens/closes.
```
Copycss
/* style.css */
.collapsible__content {
overflow: hidden;
animation: slideUp 300ms ease-out;
}
.collapsible__content[data-expanded] {
animation: slideDown 300ms ease-out;
}
@keyframes slideDown {
from {
height: 0;
}
to {
height: var(--kb-collapsible-content-height);
}
}
@keyframes slideUp {
from {
height: var(--kb-collapsible-content-height);
}
to {
height: 0;
}
}
```
```
Copycss
/* style.css */
.collapsible__content {
overflow: hidden;
animation: slideUp 300ms ease-out;
}
.collapsible__content[data-expanded] {
animation: slideDown 300ms ease-out;
}
@keyframes slideDown {
from {
height: 0;
}
to {
height: var(--kb-collapsible-content-height);
}
}
@keyframes slideUp {
from {
height: var(--kb-collapsible-content-height);
}
to {
height: 0;
}
}
```
## API Reference
### Collapsible
`Collapsible` is equivalent to the `Root` import from `@kobalte/core/collapsible` (and deprecated `Collapsible.Root`).
| Prop | Description |
| --- | --- |
| open | `boolean`<br> The controlled open state of the collapsible. |
| defaultOpen | `boolean`<br> The default open state when initially rendered. Useful when you do not need to control the open state. |
| onOpenChange | `(open: boolean) => void`<br> Event handler called when the open state of the collapsible changes. |
| disabled | `boolean`<br> Whether the collapsible is disabled. |
| forceMount | `boolean`<br> Used to force mounting the collapsible content when more control is needed. Useful when controlling animation with SolidJS animation libraries. |
| Data attribute | Description |
| --- | --- |
| data-expanded | Present when the collapsible is expanded. |
| data-closed | Present when the collapsible is collapsed. |
| data-disabled | Present when the collapsible is disabled. |
`Collapsible.Trigger` and `Collapsible.Content` share the same data-attributes.
## Rendered elements
| Component | Default rendered element |
| --- | --- |
| `Collapsible` | `div` |
| `Collapsible.Trigger` | `button` |
| `Collapsible.Content` | `div` |
## Accessibility
### Keyboard Interactions
| Key | Description |
| --- | --- |
| `Space` | When focus is on the trigger, opens/closes the collapsible. |
| `Enter` | When focus is on the trigger, opens/closes the collapsible. |
Previous[←Checkbox](https://kobalte.dev/docs/core/components/checkbox)Next[Color Area→](https://kobalte.dev/docs/core/components/color-area)

View File

@ -0,0 +1,493 @@
# Color Channel Field
A number input that allow users to edit individual color channel value.
## Import
```
Copyts
import { ColorChannelField } from "@kobalte/core/color-channel-field";
// or
import { Root, Label, ... } from "@kobalte/core/color-channel-field";
```
```
Copyts
import { ColorChannelField } from "@kobalte/core/color-channel-field";
// or
import { Root, Label, ... } from "@kobalte/core/color-channel-field";
```
## Features
- Follows the [WAI ARIA Spinbutton](https://www.w3.org/WAI/ARIA/apg/patterns/spinbutton/) design pattern.
- Built with a native `<input>` element.
- Visual and ARIA labeling support.
- Required and invalid states exposed to assistive technology via ARIA.
- Support for description and error message help text linked to the input via ARIA.
- Syncs with form reset events.
- Can be controlled or uncontrolled.
- Supports increment and decrement buttons.
- Format and localize input number and raw input.
- Supports mouse wheel event and all keyboard events.
## Anatomy
The color channel field consists of:
- **ColorChannelField**: The root container for the color channel field.
- **ColorChannelField.Label**: The label that gives the user information on the color channel field.
- **ColorChannelField.Input**: The native HTML input of the color channel field, used for display number.
- **ColorChannelField.HiddenInput**: The native HTML input of the color channel field, used for raw number form submition.
- **ColorChannelField.IncrementTrigger**: The increment button of the color channel field.
- **ColorChannelField.DecrementTrigger**: The decrement button of the color channel field.
- **ColorChannelField.Description**: The description that gives the user more information on the color channel field.
- **ColorChannelField.ErrorMessage**: The error message that gives the user information about how to fix a validation error on a color channel field.
```
Copytsx
<ColorChannelField>
<ColorChannelField.Label />
<ColorChannelField.Input />
<ColorChannelField.HiddenInput />
<ColorChannelField.IncrementTrigger />
<ColorChannelField.DecrementTrigger />
<ColorChannelField.Description />
<ColorChannelField.ErrorMessage />
</ColorChannelField>
```
```
Copytsx
<ColorChannelField>
<ColorChannelField.Label />
<ColorChannelField.Input />
<ColorChannelField.HiddenInput />
<ColorChannelField.IncrementTrigger />
<ColorChannelField.DecrementTrigger />
<ColorChannelField.Description />
<ColorChannelField.ErrorMessage />
</ColorChannelField>
```
## Example
Hue
ArrowArrow
index.tsxstyle.css
```
Copytsx
import { ColorChannelField } from "@kobalte/core/color-channel-field";
import { parseColor } from "@kobalte/utils";
import "./style.css";
function App() {
return (
<ColorChannelField class={style["color-channel-field"]} channel="hue">
<ColorChannelField.Label class={style["color-channel-field__label"]}>
Hue
</ColorChannelField.Label>
<div class={style["color-channel-field__group"]}>
<ColorChannelField.Input class={style["color-channel-field__input"]} />
<ColorChannelField.IncrementTrigger
aria-label="Increment"
class={style["color-channel-field__increment"]}
>
<ArrowIcon />
</ColorChannelField.IncrementTrigger>
<ColorChannelField.DecrementTrigger
aria-label="Decrement"
class={style["color-channel-field__decrement"]}
>
<ArrowIcon style="transform: rotate(180deg);" />
</ColorChannelField.DecrementTrigger>
</div>
</ColorChannelField>
);
}
```
```
Copytsx
import { ColorChannelField } from "@kobalte/core/color-channel-field";
import { parseColor } from "@kobalte/utils";
import "./style.css";
function App() {
return (
<ColorChannelField class={style["color-channel-field"]} channel="hue">
<ColorChannelField.Label class={style["color-channel-field__label"]}>
Hue
</ColorChannelField.Label>
<div class={style["color-channel-field__group"]}>
<ColorChannelField.Input class={style["color-channel-field__input"]} />
<ColorChannelField.IncrementTrigger
aria-label="Increment"
class={style["color-channel-field__increment"]}
>
<ArrowIcon />
</ColorChannelField.IncrementTrigger>
<ColorChannelField.DecrementTrigger
aria-label="Decrement"
class={style["color-channel-field__decrement"]}
>
<ArrowIcon style="transform: rotate(180deg);" />
</ColorChannelField.DecrementTrigger>
</div>
</ColorChannelField>
);
}
```
## Usage
The value provided to `defaultValue` or `value` should be `Color` object. You can obtain a Color object by using the `parseColor` function to parse a color from a string. The `channel` prop must also be provided to specify which color channel the field manipulates.
This must be one of the channels included in the color value, for example, for RGB colors, the "red", "green", and "blue" channels are available.
### Default value
Saturation
ArrowArrow
```
Copytsx
<ColorChannelField defaultValue={parseColor("hsl(200, 98%, 39%)")} channel="saturation">
<ColorChannelField.Label>Saturation</ColorChannelField.Label>
<div>
<ColorChannelField.Input />
<ColorChannelField.IncrementTrigger />
<ColorChannelField.DecrementTrigger />
</div>
</ColorChannelField>
```
```
Copytsx
<ColorChannelField defaultValue={parseColor("hsl(200, 98%, 39%)")} channel="saturation">
<ColorChannelField.Label>Saturation</ColorChannelField.Label>
<div>
<ColorChannelField.Input />
<ColorChannelField.IncrementTrigger />
<ColorChannelField.DecrementTrigger />
</div>
</ColorChannelField>
```
### Controlled value
Lightness
ArrowArrow
Lightness: 39%
```
Copytsx
import { createSignal } from "solid-js";
function ControlledExample() {
const [value, setValue] = createSignal(parseColor("hsl(200, 98%, 39%)"));
return (
<>
<ColorChannelField value={value()} onChange={setValue}>
<ColorChannelField.Label>Lightness</ColorChannelField.Label>
<div>
<ColorChannelField.Input />
<ColorChannelField.IncrementTrigger />
<ColorChannelField.DecrementTrigger />
</div>
</ColorChannelField>
<p>Lightness: {value().getChannelValue("lightness")}%</p>
</>
);
}
```
```
Copytsx
import { createSignal } from "solid-js";
function ControlledExample() {
const [value, setValue] = createSignal(parseColor("hsl(200, 98%, 39%)"));
return (
<>
<ColorChannelField value={value()} onChange={setValue}>
<ColorChannelField.Label>Lightness</ColorChannelField.Label>
<div>
<ColorChannelField.Input />
<ColorChannelField.IncrementTrigger />
<ColorChannelField.DecrementTrigger />
</div>
</ColorChannelField>
<p>Lightness: {value().getChannelValue("lightness")}%</p>
</>
);
}
```
### Description
The `ColorChannelField.Description` component can be used to associate additional help text with a color channel field.
Hue
ArrowArrow
Enter your favorite hue.
```
Copytsx
<ColorChannelField defaultValue={parseColor("hsl(0, 98%, 39%)")} channel="hue">
<ColorChannelField.Label>Hue</ColorChannelField.Label>
<div>
<ColorChannelField.Input />
<ColorChannelField.IncrementTrigger />
<ColorChannelField.DecrementTrigger />
</div>
<ColorChannelField.Description>Enter your favorite hue.</ColorChannelField.Description>
</ColorChannelField>
```
```
Copytsx
<ColorChannelField defaultValue={parseColor("hsl(0, 98%, 39%)")} channel="hue">
<ColorChannelField.Label>Hue</ColorChannelField.Label>
<div>
<ColorChannelField.Input />
<ColorChannelField.IncrementTrigger />
<ColorChannelField.DecrementTrigger />
</div>
<ColorChannelField.Description>Enter your favorite hue.</ColorChannelField.Description>
</ColorChannelField>
```
### Error message
The `ColorChannelField.ErrorMessage` component can be used to help the user fix a validation error. It should be combined with the `validationState` prop to semantically mark the field as invalid for assistive technologies.
By default, it will render only when the `validationState` prop is set to `invalid`, use the `forceMount` prop to always render the error message (ex: for usage with animation libraries).
Saturation
ArrowArrow
Hmm, I prefer 40% saturation.
```
Copytsx
import { createSignal } from "solid-js";
function ErrorMessageExample() {
const [value, setValue] = createSignal(parseColor("hsl(200, 30%, 39%)"));
return (
<ColorChannelField
value={value()}
channel="saturation"
onChange={setValue}
validationState={value().getChannelValue("saturation") !== 40 ? "invalid" : "valid"}
>
<ColorChannelField.Label>Saturation</ColorChannelField.Label>
<div>
<ColorChannelField.Input />
<ColorChannelField.IncrementTrigger />
<ColorChannelField.DecrementTrigger />
</div>
<ColorChannelField.ErrorMessage>Hmm, I prefer 40% saturation.</ColorChannelField.ErrorMessage>
</ColorChannelField>
);
}
```
```
Copytsx
import { createSignal } from "solid-js";
function ErrorMessageExample() {
const [value, setValue] = createSignal(parseColor("hsl(200, 30%, 39%)"));
return (
<ColorChannelField
value={value()}
channel="saturation"
onChange={setValue}
validationState={value().getChannelValue("saturation") !== 40 ? "invalid" : "valid"}
>
<ColorChannelField.Label>Saturation</ColorChannelField.Label>
<div>
<ColorChannelField.Input />
<ColorChannelField.IncrementTrigger />
<ColorChannelField.DecrementTrigger />
</div>
<ColorChannelField.ErrorMessage>Hmm, I prefer 40% saturation.</ColorChannelField.ErrorMessage>
</ColorChannelField>
);
}
```
### HTML forms
The color channel field `name` prop along with `<ColorChannelField.HiddenInput/>` can be used for integration with HTML forms. Only the raw value is passed to the form.
If the formatted value is wanted (unrecommended) set the `name` attribute on `<ColorChannelField.Input/>`.
Hue
ArrowArrow
ResetSubmit
```
Copytsx
function HTMLFormExample() {
const onSubmit = (e: SubmitEvent) => {
// handle form submission.
};
return (
<form onSubmit={onSubmit}>
<ColorChannelField name="hue" defaultValue={parseColor("hsl(10, 98%, 39%)")} channel="hue">
<ColorChannelField.Label>Hue</ColorChannelField.Label>
<ColorChannelField.HiddenInput />
<div>
<ColorChannelField.Input />
<ColorChannelField.IncrementTrigger />
<ColorChannelField.DecrementTrigger />
</div>
</ColorChannelField>
<div>
<button type="reset">Reset</button>
<button type="submit">Submit</button>
</div>
</form>
);
}
```
```
Copytsx
function HTMLFormExample() {
const onSubmit = (e: SubmitEvent) => {
// handle form submission.
};
return (
<form onSubmit={onSubmit}>
<ColorChannelField name="hue" defaultValue={parseColor("hsl(10, 98%, 39%)")} channel="hue">
<ColorChannelField.Label>Hue</ColorChannelField.Label>
<ColorChannelField.HiddenInput />
<div>
<ColorChannelField.Input />
<ColorChannelField.IncrementTrigger />
<ColorChannelField.DecrementTrigger />
</div>
</ColorChannelField>
<div>
<button type="reset">Reset</button>
<button type="submit">Submit</button>
</div>
</form>
);
}
```
### Triggers
The color channel field supports optional increment/decrement triggers that are easily customizable.
Brightness
-+
```
Copytsx
<ColorChannelField defaultValue={parseColor("hsb(10, 98%, 50%)")} channel="brightness">
<ColorChannelField.Label>Brightness</ColorChannelField.Label>
<div>
<ColorChannelField.DecrementTrigger class="custom-trigger">
-
</ColorChannelField.DecrementTrigger>
<ColorChannelField.Input />
<ColorChannelField.IncrementTrigger class="custom-trigger">
+
</ColorChannelField.IncrementTrigger>
</div>
</ColorChannelField>
```
```
Copytsx
<ColorChannelField defaultValue={parseColor("hsb(10, 98%, 50%)")} channel="brightness">
<ColorChannelField.Label>Brightness</ColorChannelField.Label>
<div>
<ColorChannelField.DecrementTrigger class="custom-trigger">
-
</ColorChannelField.DecrementTrigger>
<ColorChannelField.Input />
<ColorChannelField.IncrementTrigger class="custom-trigger">
+
</ColorChannelField.IncrementTrigger>
</div>
</ColorChannelField>
```
## API Reference
### ColorChannelField
`ColorChannelField` is equivalent to the `Root` import from `@kobalte/core/color-channel-field`.
| Prop | Description |
| --- | --- |
| value | `Color`<br> The controlled value of the field. |
| defaultValue | `Color`<br> The default value when initially rendered. Useful when you do not need to control the value. |
| colorSpace | `ColorSpace`<br> The color space that the field operates in. The `channel` must be in this color space. |
| onChange | `(value: Color) => void`<br> Event handler called when the value of the field changes. |
| minValue | `number`<br> The smallest value allowed in the number field. |
| maxValue | `number`<br> The largest value allowed in the number field. |
| step | `number`<br> Increment/Decrement step when using the triggers or the arrows on keyboard in the field. |
| largeStep | `number`<br> Increment/Decrement step when using the Page UP/Down keys in the field, defaults `10 * step`. |
| changeOnWheel | `boolean`<br> Whether to increment/decrement on wheel scroll inside the field. |
| format | `boolean`<br> Whether to format the input value. |
| name | `string`<br> The name of the ColorChannelField.HiddenInput of the field, used when submitting an HTML form. See [MDN](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#htmlattrdefname). |
| validationState | `'valid' | 'invalid'`<br> Whether the field should display its "valid" or "invalid" visual styling. |
| required | `boolean`<br> Whether the user must fill the field before the owning form can be submitted. |
| disabled | `boolean`<br> Whether the field is disabled. |
| readOnly | `boolean`<br> Whether the field items can be selected but not changed by the user. |
| Data attribute | Description |
| --- | --- |
| data-valid | Present when the field is valid according to the validation rules. |
| data-invalid | Present when the field is invalid according to the validation rules. |
| data-required | Present when the user must fill the field before the owning form can be submitted. |
| data-disabled | Present when the field is disabled. |
| data-readonly | Present when the field is read only. |
`ColorChannelField.Label`, `ColorChannelField.Input`, `ColorChannelField.HiddenInput`, `ColorChannelField.Description` and `ColorChannelField.ErrorMesssage` share the same data-attributes.
### ColorChannelField.ErrorMessage
| Prop | Description |
| --- | --- |
| forceMount | `boolean`<br> Used to force mounting when more control is needed. Useful when controlling animation with SolidJS animation libraries. |
## Rendered elements
| Component | Default rendered element |
| --- | --- |
| `ColorChannelField` | `div` |
| `ColorChannelField.Label` | `label` |
| `ColorChannelField.Input` | `input` |
| `ColorChannelField.HiddenInput` | `input` |
| `ColorChannelField.IncrementTrigger` | `button` |
| `ColorChannelField.DecrementTrigger` | `button` |
| `ColorChannelField.Description` | `div` |
| `ColorChannelField.ErrorMessage` | `div` |
Previous[←Color Area](https://kobalte.dev/docs/core/components/color-area)Next[Color Field→](https://kobalte.dev/docs/core/components/color-field)

View File

@ -0,0 +1,336 @@
# Color Field
Allows users to enter and adjust a hex color value.
## Import
```
Copyts
import { ColorField } from "@kobalte/core/color-field";
// or
import { Root, Label, ... } from "@kobalte/core/color-field";
```
```
Copyts
import { ColorField } from "@kobalte/core/color-field";
// or
import { Root, Label, ... } from "@kobalte/core/color-field";
```
## Features
- Support for parsing and formatting a hex color value.
- Validates keyboard entry as the user types so that only valid hex characters are accepted.
- Visual and ARIA labeling support.
- Required and invalid states exposed to assistive technology via ARIA.
- Support for description and error message help text linked to the input via ARIA.
- Syncs with form reset events.
- Can be controlled or uncontrolled.
## Anatomy
The color field consists of:
- **ColorField**: The root container for the color field.
- **ColorField.Label**: The label that gives the user information on the color field.
- **ColorField.Input**: The native HTML input of the color field.
- **ColorField.Description**: The description that gives the user more information on the color field.
- **ColorField.ErrorMessage**: The error message that gives the user information about how to fix a validation error on the color field.
```
Copytsx
<ColorField>
<ColorField.Label />
<ColorField.Input />
<ColorField.Description />
<ColorField.ErrorMessage />
</ColorField>
```
```
Copytsx
<ColorField>
<ColorField.Label />
<ColorField.Input />
<ColorField.Description />
<ColorField.ErrorMessage />
</ColorField>
```
## Example
Favorite hex color
index.tsxstyle.css
```
Copytsx
import { ColorField } from "@kobalte/core/color-field";
import "./style.css";
function App() {
return (
<ColorField class="color-field">
<ColorField.Label class="color-field__label">Favorite hex color</ColorField.Label>
<ColorField.Input class="color-field__input" />
</ColorField>
);
}
```
```
Copytsx
import { ColorField } from "@kobalte/core/color-field";
import "./style.css";
function App() {
return (
<ColorField class="color-field">
<ColorField.Label class="color-field__label">Favorite hex color</ColorField.Label>
<ColorField.Input class="color-field__input" />
</ColorField>
);
}
```
## Usage
### Default value
An initial, uncontrolled value can be provided using the `defaultValue` prop.
Favorite hex color
```
Copytsx
<ColorField class="color-field" defaultValue="#7f007f">
<ColorField.Label class="color-field__label">Favorite hex color</ColorField.Label>
<ColorField.Input class="color-field__input" />
</ColorField>
```
```
Copytsx
<ColorField class="color-field" defaultValue="#7f007f">
<ColorField.Label class="color-field__label">Favorite hex color</ColorField.Label>
<ColorField.Input class="color-field__input" />
</ColorField>
```
### Controlled value
The `value` prop can be used to make the value controlled. The `onChange` event is fired when the user type into the input and receive the new value.
Favorite hex color
Your favorite color is: #7f007f
```
Copytsx
import { createSignal } from "solid-js";
function ControlledExample() {
const [value, setValue] = createSignal("#7f007f");
return (
<>
<ColorField class="color-field" value={value()} onChange={setValue}>
<ColorField.Label class="color-field__label">Favorite hex color</ColorField.Label>
<ColorField.Input class="color-field__input" />
</ColorField>
<p>Your favorite color is: {value()}</p>
</>
);
}
```
```
Copytsx
import { createSignal } from "solid-js";
function ControlledExample() {
const [value, setValue] = createSignal("#7f007f");
return (
<>
<ColorField class="color-field" value={value()} onChange={setValue}>
<ColorField.Label class="color-field__label">Favorite hex color</ColorField.Label>
<ColorField.Input class="color-field__input" />
</ColorField>
<p>Your favorite color is: {value()}</p>
</>
);
}
```
### Description
The `ColorField.Description` component can be used to associate additional help text with a color field.
Favorite hex color
Choose the color you like the most.
```
Copytsx
<ColorField class="color-field">
<ColorField.Label class="color-field__label">Favorite hex color</ColorField.Label>
<ColorField.Input class="color-field__input" />
<ColorField.Description class="color-field__description">
Choose the color you like the most.
</ColorField.Description>
</ColorField>
```
```
Copytsx
<ColorField class="color-field">
<ColorField.Label class="color-field__label">Favorite hex color</ColorField.Label>
<ColorField.Input class="color-field__input" />
<ColorField.Description class="color-field__description">
Choose the color you like the most.
</ColorField.Description>
</ColorField>
```
### Error message
The `ColorField.ErrorMessage` component can be used to help the user fix a validation error. It should be combined with the `validationState` prop to semantically mark the color field as invalid for assistive technologies.
By default, it will render only when the `validationState` prop is set to `invalid`, use the `forceMount` prop to always render the error message (ex: for usage with animation libraries).
Favorite hex color
Hmm, I prefer black.
```
Copytsx
import { createSignal } from "solid-js";
function ErrorMessageExample() {
const [value, setValue] = createSignal("#7f007f");
return (
<ColorField
value={value()}
onChange={setValue}
validationState={value() !== "#000000" ? "invalid" : "valid"}
>
<ColorField.Label>Favorite hex color</ColorField.Label>
<ColorField.Input />
<ColorField.ErrorMessage>Hmm, I prefer black.</ColorField.ErrorMessage>
</ColorField>
);
}
```
```
Copytsx
import { createSignal } from "solid-js";
function ErrorMessageExample() {
const [value, setValue] = createSignal("#7f007f");
return (
<ColorField
value={value()}
onChange={setValue}
validationState={value() !== "#000000" ? "invalid" : "valid"}
>
<ColorField.Label>Favorite hex color</ColorField.Label>
<ColorField.Input />
<ColorField.ErrorMessage>Hmm, I prefer black.</ColorField.ErrorMessage>
</ColorField>
);
}
```
### HTML forms
The color field `name` prop can be used for integration with HTML forms.
Favorite hex color
ResetSubmit
```
Copytsx
function HTMLFormExample() {
const onSubmit = (e: SubmitEvent) => {
// handle form submission.
};
return (
<form onSubmit={onSubmit}>
<ColorField name="favorite-color">
<ColorField.Label>Favorite hex color</ColorField.Label>
<ColorField.Input />
</ColorField>
</form>
);
}
```
```
Copytsx
function HTMLFormExample() {
const onSubmit = (e: SubmitEvent) => {
// handle form submission.
};
return (
<form onSubmit={onSubmit}>
<ColorField name="favorite-color">
<ColorField.Label>Favorite hex color</ColorField.Label>
<ColorField.Input />
</ColorField>
</form>
);
}
```
## API Reference
### ColorField
`ColorField` is equivalent to the `Root` import from `@kobalte/core/color-field`.
| Prop | Description |
| --- | --- |
| value | `string`<br> The controlled value of the color field to check. |
| defaultValue | `string`<br> The default value when initially rendered. Useful when you do not need to control the value. |
| onChange | `(value: string) => void`<br> Event handler called when the value of the color field changes. |
| name | `string`<br> The name of the color field, used when submitting an HTML form. See [MDN](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#htmlattrdefname). |
| validationState | `'valid' | 'invalid'`<br> Whether the color field should display its "valid" or "invalid" visual styling. |
| required | `boolean`<br> Whether the user must fill the color field before the owning form can be submitted. |
| disabled | `boolean`<br> Whether the color field is disabled. |
| readOnly | `boolean`<br> Whether the color field items can be selected but not changed by the user. |
| Data attribute | Description |
| --- | --- |
| data-valid | Present when the color field is valid according to the validation rules. |
| data-invalid | Present when the color field is invalid according to the validation rules. |
| data-required | Present when the user must fill the color field before the owning form can be submitted. |
| data-disabled | Present when the color field is disabled. |
| data-readonly | Present when the color field is read only. |
`ColorField.Label`, `ColorField.Input`, `ColorField.Description` and `ColorField.ErrorMesssage` share the same data-attributes.
### ColorField.ErrorMessage
| Prop | Description |
| --- | --- |
| forceMount | `boolean`<br> Used to force mounting when more control is needed. Useful when controlling animation with SolidJS animation libraries. |
## Rendered elements
| Component | Default rendered element |
| --- | --- |
| `ColorField` | `div` |
| `ColorField.Label` | `label` |
| `ColorField.Input` | `input` |
| `ColorField.Description` | `div` |
| `ColorField.ErrorMessage` | `div` |
Previous[←Color Channel Field](https://kobalte.dev/docs/core/components/color-channel-field)Next[Color Slider→](https://kobalte.dev/docs/core/components/color-slider)

View File

@ -0,0 +1,409 @@
# Color Slider
Adjusts an individual channel of a color value.
## Import
```
Copyts
import { ColorSlider } from "@kobalte/core/color-slider";
// or
import { Root, Track, ... } from "@kobalte/core/color-slider";
```
```
Copyts
import { ColorSlider } from "@kobalte/core/color-slider";
// or
import { Root, Track, ... } from "@kobalte/core/color-slider";
```
## Features
- Can be controlled or uncontrolled.
- Localized color descriptions for screen reader users.
- Support for adjusting a single channel of RGBA, HSLA and HSBA colors, in both horizontal and vertical orientations.
- Support click or touch on track to change value.
- Support right or left direction.
- Support for custom value label.
## Anatomy
The color slider consists of:
- **ColorSlider:** The root container for the color slider.
- **ColorSlider.Track:** The component that visually represents the color slider track.
- **ColorSlider.Thumb:** The thumb that is used to visually indicate a value in the color slider.
- **ColorSlider.Input:** The native html input that is visually hidden in the color slider thumb.
- **ColorSlider.Label:** The label that gives the user information on the color slider.
- **ColorSlider.ValueLabel:** The accessible label text representing the current value in a human-readable format.
```
Copytsx
<ColorSlider>
<ColorSlider.Label />
<ColorSlider.ValueLabel />
<ColorSlider.Track>
<ColorSlider.Thumb>
<ColorSlider.Input />
</ColorSlider.Thumb>
</ColorSlider.Track>
</ColorSlider>
```
```
Copytsx
<ColorSlider>
<ColorSlider.Label />
<ColorSlider.ValueLabel />
<ColorSlider.Track>
<ColorSlider.Thumb>
<ColorSlider.Input />
</ColorSlider.Thumb>
</ColorSlider.Track>
</ColorSlider>
```
## Example
Blue
197
index.tsxstyle.css
```
Copytsx
import { ColorSlider } from "@kobalte/core/color-slider";
import { parseColor } from "@kobalte/utils";
import "./style.css";
function App() {
return (
<ColorSlider class="ColorSliderRoot" channel="blue">
<div class="ColorSliderLabel">
<ColorSlider.Label>Blue</ColorSlider.Label>
<ColorSlider.ValueLabel />
</div>
<ColorSlider.Track class="ColorSliderTrack">
<ColorSlider.Thumb class="ColorSliderThumb">
<ColorSlider.Input />
</ColorSlider.Thumb>
</ColorSlider.Track>
</ColorSlider>
);
}
```
```
Copytsx
import { ColorSlider } from "@kobalte/core/color-slider";
import { parseColor } from "@kobalte/utils";
import "./style.css";
function App() {
return (
<ColorSlider class="ColorSliderRoot" channel="blue">
<div class="ColorSliderLabel">
<ColorSlider.Label>Blue</ColorSlider.Label>
<ColorSlider.ValueLabel />
</div>
<ColorSlider.Track class="ColorSliderTrack">
<ColorSlider.Thumb class="ColorSliderThumb">
<ColorSlider.Input />
</ColorSlider.Thumb>
</ColorSlider.Track>
</ColorSlider>
);
}
```
## Usage
The value provided to `defaultValue` or `value` should be `Color` object. You can obtain a Color object by using the `parseColor` function to parse a color from a string. The `channel` prop must also be provided to specify which color channel the slider should display.
This must be one of the channels included in the color value, for example, for RGB colors, the "red", "green", and "blue" channels are available.
### Default value
Hue
index.tsxstyle.css
```
Copytsx
import { ColorSlider } from "@kobalte/core/color-slider";
import { parseColor } from "@kobalte/utils";
import "./style.css";
function App() {
return (
<ColorSlider class="ColorSliderRoot" defaultValue={parseColor("hsl(0, 100%, 50%)")} channel="hue">
<div class="ColorSliderLabel">
<ColorSlider.Label>Hue</ColorSlider.Label>
<ColorSlider.ValueLabel />
</div>
<ColorSlider.Track class="ColorSliderTrack">
<ColorSlider.Thumb class="ColorSliderThumb">
<ColorSlider.Input />
</ColorSlider.Thumb>
</ColorSlider.Track>
</ColorSlider>
);
}
```
```
Copytsx
import { ColorSlider } from "@kobalte/core/color-slider";
import { parseColor } from "@kobalte/utils";
import "./style.css";
function App() {
return (
<ColorSlider class="ColorSliderRoot" defaultValue={parseColor("hsl(0, 100%, 50%)")} channel="hue">
<div class="ColorSliderLabel">
<ColorSlider.Label>Hue</ColorSlider.Label>
<ColorSlider.ValueLabel />
</div>
<ColorSlider.Track class="ColorSliderTrack">
<ColorSlider.Thumb class="ColorSliderThumb">
<ColorSlider.Input />
</ColorSlider.Thumb>
</ColorSlider.Track>
</ColorSlider>
);
}
```
### Controlled value
Hue
index.tsxstyle.css
```
Copytsx
import { createSignal } from "solid-js";
import { ColorSlider } from "@kobalte/core/color-slider";
import { parseColor } from "@kobalte/utils";
import "./style.css";
function App() {
const [value, setValue] = createSignal(parseColor('hsl(0, 100%, 50%)'));
return (
<ColorSlider class="ColorSliderRoot" value={value()} onChange={setValue} channel="hue">
<div class="ColorSliderLabel">
<ColorSlider.Label>Hue</ColorSlider.Label>
<ColorSlider.ValueLabel />
</div>
<ColorSlider.Track class="ColorSliderTrack">
<ColorSlider.Thumb class="ColorSliderThumb">
<ColorSlider.Input />
</ColorSlider.Thumb>
</ColorSlider.Track>
</ColorSlider>
);
}
```
```
Copytsx
import { createSignal } from "solid-js";
import { ColorSlider } from "@kobalte/core/color-slider";
import { parseColor } from "@kobalte/utils";
import "./style.css";
function App() {
const [value, setValue] = createSignal(parseColor('hsl(0, 100%, 50%)'));
return (
<ColorSlider class="ColorSliderRoot" value={value()} onChange={setValue} channel="hue">
<div class="ColorSliderLabel">
<ColorSlider.Label>Hue</ColorSlider.Label>
<ColorSlider.ValueLabel />
</div>
<ColorSlider.Track class="ColorSliderTrack">
<ColorSlider.Thumb class="ColorSliderThumb">
<ColorSlider.Input />
</ColorSlider.Thumb>
</ColorSlider.Track>
</ColorSlider>
);
}
```
### Vertical Slider
Red
255
index.tsxstyle.css
```
Copytsx
import { ColorSlider } from "@kobalte/core/color-slider";
import { parseColor } from "@kobalte/utils";
import "./style.css";
function App() {
return (
<ColorSlider class="ColorSliderRoot" defaultValue={parseColor('#ff00ff')} channel="red" orientation="vertical">
<div class="ColorSliderLabel">
<ColorSlider.Label>Hue</ColorSlider.Label>
<ColorSlider.ValueLabel />
</div>
<ColorSlider.Track class="ColorSliderTrack">
<ColorSlider.Thumb class="ColorSliderThumb">
<ColorSlider.Input />
</ColorSlider.Thumb>
</ColorSlider.Track>
</ColorSlider>
);
}
```
```
Copytsx
import { ColorSlider } from "@kobalte/core/color-slider";
import { parseColor } from "@kobalte/utils";
import "./style.css";
function App() {
return (
<ColorSlider class="ColorSliderRoot" defaultValue={parseColor('#ff00ff')} channel="red" orientation="vertical">
<div class="ColorSliderLabel">
<ColorSlider.Label>Hue</ColorSlider.Label>
<ColorSlider.ValueLabel />
</div>
<ColorSlider.Track class="ColorSliderTrack">
<ColorSlider.Thumb class="ColorSliderThumb">
<ColorSlider.Input />
</ColorSlider.Thumb>
</ColorSlider.Track>
</ColorSlider>
);
}
```
### Custom Value Label
rgb(255, 0, 255)
index.tsxstyle.css
```
Copytsx
import { ColorSlider } from "@kobalte/core/color-slider";
import { parseColor } from "@kobalte/utils";
import "./style.css";
function App() {
return (
<ColorSlider class="ColorSliderRoot" defaultValue={parseColor("#ff00ff")} channel="green" getValueLabel={param => `${param.toFormat("rgb")}`}>
<div class="ColorSliderLabel">
<ColorSlider.ValueLabel />
</div>
<ColorSlider.Track class="ColorSliderTrack">
<ColorSlider.Thumb class="ColorSliderThumb">
<ColorSlider.Input />
</ColorSlider.Thumb>
</ColorSlider.Track>
</ColorSlider>
);
}
```
```
Copytsx
import { ColorSlider } from "@kobalte/core/color-slider";
import { parseColor } from "@kobalte/utils";
import "./style.css";
function App() {
return (
<ColorSlider class="ColorSliderRoot" defaultValue={parseColor("#ff00ff")} channel="green" getValueLabel={param => `${param.toFormat("rgb")}`}>
<div class="ColorSliderLabel">
<ColorSlider.ValueLabel />
</div>
<ColorSlider.Track class="ColorSliderTrack">
<ColorSlider.Thumb class="ColorSliderThumb">
<ColorSlider.Input />
</ColorSlider.Thumb>
</ColorSlider.Track>
</ColorSlider>
);
}
```
## API Reference
### ColorSlider
`ColorSlider` is equivalent to the `Root` import from `@kobalte/core/color-slider`.
| Prop | Description |
| --- | --- |
| value | `Color`<br> The controlled value of the slider. Must be used in conjunction with `onChange`. |
| defaultValue | `Color`<br> The value of the slider when initially rendered. Use when you do not need to control the state of the slider. |
| channel | `ColorChannel`<br> The color channel that the slider manipulates. |
| colorSpace | `ColorSpace`<br> The color space that the slider operates in. The `channel` must be in this color space. |
| onChange | `(value: Color) => void`<br> Event handler called when the value changes. |
| onChangeEnd | `(value: Color) => void`<br> Event handler called when the value changes at the end of an interaction. |
| getValueLabel | `(param: Color) => string`<br> A function to get the accessible label text representing the current value in a human-readable format. |
| orientation | `'horizontal' | 'vertical'`<br> The orientation of the slider. |
| name | `string`<br> The name of the slider, used when submitting an HTML form. |
| validationState | `'valid' | 'invalid'`<br> Whether the slider should display its "valid" or "invalid" visual styling. |
| required | `boolean`<br> Whether the user must check a radio group item before the owning form can be submitted. |
| disabled | `boolean`<br> Whether the radio group is disabled. |
| readOnly | `boolean`<br> Whether the radio group items can be selected but not changed by the user. |
| translations | `ColorIntlTranslations`<br> Localization strings. |
| Data attribute | Description |
| --- | --- |
| data-orientation='horizontal' | Present when the slider has horizontal orientation. |
| data-orientation='vertical' | Present when the slider has vertical orientation. |
| data-valid | Present when the slider is valid according to the validation rules. |
| data-invalid | Present when the slider is invalid according to the validation rules. |
| data-required | Present when the user must slider an item before the owning form can be submitted. |
| data-disabled | Present when the slider is disabled. |
| data-readonly | Present when the slider is read only. |
`ColorSlider.ValueLabel`, `ColorSlider.Input`, `ColorSlider.Thumb` and `ColorSlider.Track` share the same data-attributes.
### ColorSlider.Thumb
The current color is available on the thumb using the custom css property `--kb-color-current`.
## Rendered elements
| Component | Default rendered element |
| --- | --- |
| `ColorSlider` | `div` |
| `ColorSlider.Track` | `div` |
| `ColorSlider.Thumb` | `span` |
| `ColorSlider.Input` | `input` |
| `ColorSlider.ValueLabel` | `div` |
| `ColorSlider.Label` | `label` |
| `ColorSlider.Description` | `div` |
| `ColorSlider.ErrorMessage` | `div` |
## Accessibility
### Keyboard Interactions
| Key | Description |
| --- | --- |
| `PageUp` | Increases the value of the thumb by a larger step. |
| `PageDown` | Decreases the value of the thumb by a larger step. |
| `ArrowDown` | Decreases the value of the thumb by the step amount. |
| `ArrowUp` | Increases the value of the thumb by the step amount. |
| `ArrowRight` | Increments/decrements by the step value depending on `orientation`. |
| `ArrowLeft` | Increments/decrements by the step value depending on `orientation`. |
| `Home` | Sets the value of the thumb to the minimum value. |
| `End` | Sets the value of the thumb to the maximum value. |
Previous[←Color Field](https://kobalte.dev/docs/core/components/color-field)Next[Color Swatch→](https://kobalte.dev/docs/core/components/color-swatch)

View File

@ -0,0 +1,153 @@
# Color Swatch
Displays a preview of a selected color.
## Import
```
Copyts
import { ColorSwatch } from "@kobalte/core/color-swatch";
// or
import { Root } from "@kobalte/core/color-swatch";
```
```
Copyts
import { ColorSwatch } from "@kobalte/core/color-swatch";
// or
import { Root } from "@kobalte/core/color-swatch";
```
## Features
- Localized color descriptions for screen reader users.
## Anatomy
The color swatch consists of:
- **ColorSwatch:** The root container for the color swatch.
```
Copytsx
<ColorSwatch />
```
```
Copytsx
<ColorSwatch />
```
## Example
index.tsxstyle.css
```
Copytsx
import { ColorSwatch } from "@kobalte/core/color-swatch";
import { parseColor } from "@kobalte/utils";
import "./style.css";
function App() {
return (
<ColorSwatch class="ColorSwatchRoot" value={parseColor("hsl(200, 98%, 39%)")} />
);
}
```
```
Copytsx
import { ColorSwatch } from "@kobalte/core/color-swatch";
import { parseColor } from "@kobalte/utils";
import "./style.css";
function App() {
return (
<ColorSwatch class="ColorSwatchRoot" value={parseColor("hsl(200, 98%, 39%)")} />
);
}
```
## Usage
ColorSwatch accepts a value via the `value` prop. The value should be `Color` object. You can obtain a Color object by using the `parseColor` function to parse a color from a string.
### Value
index.tsxstyle.css
```
Copytsx
import { ColorSwatch } from "@kobalte/core/color-swatch";
import { parseColor } from "@kobalte/utils";
import "./style.css";
function App() {
return (
<ColorSwatch class="ColorSwatchRoot" value={parseColor("#7f0000")} />
);
}
```
```
Copytsx
import { ColorSwatch } from "@kobalte/core/color-swatch";
import { parseColor } from "@kobalte/utils";
import "./style.css";
function App() {
return (
<ColorSwatch class="ColorSwatchRoot" value={parseColor("#7f0000")} />
);
}
```
### Custom Color Name
index.tsxstyle.css
```
Copytsx
import { ColorSwatch } from "@kobalte/core/color-swatch";
import { parseColor } from "@kobalte/utils";
import "./style.css";
function App() {
return (
<ColorSwatch class="ColorSwatchRoot" value={parseColor("hsl(200, 98%, 39%)")} colorName="Kobalte blue" />
);
}
```
```
Copytsx
import { ColorSwatch } from "@kobalte/core/color-swatch";
import { parseColor } from "@kobalte/utils";
import "./style.css";
function App() {
return (
<ColorSwatch class="ColorSwatchRoot" value={parseColor("hsl(200, 98%, 39%)")} colorName="Kobalte blue" />
);
}
```
## API Reference
### ColorSwatch
`ColorSwatch` is equivalent to the `Root` import from `@kobalte/core/color-swatch`.
| Prop | Description |
| --- | --- |
| value | `Color`<br> The color value to display in the swatch. |
| colorName | `string`<br> A localized accessible name for the color. By default, a description is generated from the color value, but this can be overridden if you have a more specific color name (e.g. Pantone colors). |
| translations | `ColorSwatchIntlTranslations`<br> Localization strings. |
## Rendered elements
| Component | Default rendered element |
| --- | --- |
| `ColorSwatch` | `div` |
Previous[←Color Slider](https://kobalte.dev/docs/core/components/color-slider)Next[Color Wheel→](https://kobalte.dev/docs/core/components/color-wheel)

View File

@ -0,0 +1,408 @@
# Color Wheel
Allows users to adjust the hue of an HSL or HSB color value on a circular track.
## Import
```
Copyts
import { ColorWheel } from "@kobalte/core/color-wheel";
// or
import { Root, Track, ... } from "@kobalte/core/color-wheel";
```
```
Copyts
import { ColorWheel } from "@kobalte/core/color-wheel";
// or
import { Root, Track, ... } from "@kobalte/core/color-wheel";
```
## Features
- Support for adjusting the hue of an HSL or HSB color value.
- Support click or touch on track to change value.
- Support right or left direction.
- Support for custom value label.
- Localized color descriptions for screen reader users.
- Can be controlled or uncontrolled.
## Anatomy
The color wheel consists of:
- **ColorWheel:** The root container for the color wheel.
- **ColorWheel.Track:** The component that visually represents the color wheel track.
- **ColorWheel.Thumb:** The thumb that is used to visually indicate a value in the color wheel.
- **ColorWheel.Input:** The native html input that is visually hidden in the color wheel thumb.
- **ColorWheel.Label:** The label that gives the user information on the color wheel.
- **ColorWheel.ValueLabel:** The accessible label text representing the current value in a human-readable format.
```
Copytsx
<ColorWheel>
<ColorWheel.Label />
<ColorWheel.ValueLabel />
<ColorWheel.Track>
<ColorWheel.Thumb>
<ColorWheel.Input />
</ColorWheel.Thumb>
</ColorWheel.Track>
</ColorWheel>
```
```
Copytsx
<ColorWheel>
<ColorWheel.Label />
<ColorWheel.ValueLabel />
<ColorWheel.Track>
<ColorWheel.Thumb>
<ColorWheel.Input />
</ColorWheel.Thumb>
</ColorWheel.Track>
</ColorWheel>
```
## Example
Label
index.tsxstyle.css
```
Copytsx
import { ColorWheel } from "@kobalte/core/color-wheel";
import "./style.css";
function App() {
return (
<ColorWheel class="ColorWheelRoot">
<ColorWheel.Label class="ColorWheelLabel">Label</ColorWheel.Label>
<ColorWheel.Track class="ColorWheelTrack">
<ColorWheel.Thumb class="ColorWheelThumb">
<ColorWheel.Input />
</ColorWheel.Thumb>
</ColorWheel.Track>
</ColorWheel>
);
}
```
```
Copytsx
import { ColorWheel } from "@kobalte/core/color-wheel";
import "./style.css";
function App() {
return (
<ColorWheel class="ColorWheelRoot">
<ColorWheel.Label class="ColorWheelLabel">Label</ColorWheel.Label>
<ColorWheel.Track class="ColorWheelTrack">
<ColorWheel.Thumb class="ColorWheelThumb">
<ColorWheel.Input />
</ColorWheel.Thumb>
</ColorWheel.Track>
</ColorWheel>
);
}
```
## Usage
### Default value
By default, `ColorWheel` is uncontrolled with a default value of red (hue = 0). You can change the default value using the `defaultValue` prop.
Label
```
Copytsx
import { ColorWheel } from "@kobalte/core/color-wheel";
import { parseColor } from "@kobalte/core";
function App() {
return (
<ColorWheel defaultValue={parseColor("hsl(80, 100%, 50%)")}>
<ColorWheel.Label>Label</ColorWheel.Label>
<ColorWheel.Track>
<ColorWheel.Thumb>
<ColorWheel.Input />
</ColorWheel.Thumb>
</ColorWheel.Track>
</ColorWheel>
);
}
```
```
Copytsx
import { ColorWheel } from "@kobalte/core/color-wheel";
import { parseColor } from "@kobalte/core";
function App() {
return (
<ColorWheel defaultValue={parseColor("hsl(80, 100%, 50%)")}>
<ColorWheel.Label>Label</ColorWheel.Label>
<ColorWheel.Track>
<ColorWheel.Thumb>
<ColorWheel.Input />
</ColorWheel.Thumb>
</ColorWheel.Track>
</ColorWheel>
);
}
```
### Controlled value
A `ColorWheel` can be made controlled using the `value` prop. The `onChange` event is fired when the user drags the thumb and receive the new value.
Label
Current color value: hsl(0 100% 50%)
```
Copytsx
import { createSignal } from "solid-js";
function ControlledValueExample() {
const [value, setValue] = createSignal(parseColor("hsl(0, 100%, 50%)"));
return (
<>
<ColorWheel value={value()} onChange={setValue}>
<ColorWheel.Label>Label</ColorWheel.Label>
<ColorWheel.Track>
<ColorWheel.Thumb>
<ColorWheel.Input />
</ColorWheel.Thumb>
</ColorWheel.Track>
</ColorWheel>
<p>Current color value: {value().toString("hsl")}</p>
</>
);
}
```
```
Copytsx
import { createSignal } from "solid-js";
function ControlledValueExample() {
const [value, setValue] = createSignal(parseColor("hsl(0, 100%, 50%)"));
return (
<>
<ColorWheel value={value()} onChange={setValue}>
<ColorWheel.Label>Label</ColorWheel.Label>
<ColorWheel.Track>
<ColorWheel.Thumb>
<ColorWheel.Input />
</ColorWheel.Thumb>
</ColorWheel.Track>
</ColorWheel>
<p>Current color value: {value().toString("hsl")}</p>
</>
);
}
```
### Thickness
The `thickness` prop controls the width of the `ColorWheel`'s circular track. This prop is required.
Label
```
Copytsx
<ColorWheel thickness={60}>
<ColorWheel.Label>Label</ColorWheel.Label>
<ColorWheel.Track>
<ColorWheel.Thumb>
<ColorWheel.Input />
</ColorWheel.Thumb>
</ColorWheel.Track>
</ColorWheel>
```
```
Copytsx
<ColorWheel thickness={60}>
<ColorWheel.Label>Label</ColorWheel.Label>
<ColorWheel.Track>
<ColorWheel.Thumb>
<ColorWheel.Input />
</ColorWheel.Thumb>
</ColorWheel.Track>
</ColorWheel>
```
### Custom Value Label
Label
hsla(0 100% 50% / 1)
```
Copytsx
<ColorWheel
getValueLabel={color =>
color
.toFormat("hsl")
.withChannelValue("saturation", 100)
.withChannelValue("lightness", 50)
.withChannelValue("alpha", 1)
.toString()
}
>
<ColorWheel.Label>Label</ColorWheel.Label>
<ColorWheel.Track>
<ColorWheel.Thumb>
<ColorWheel.Input />
</ColorWheel.Thumb>
</ColorWheel.Track>
<div>
<ColorWheel.ValueLabel />
</div>
</ColorWheel>
```
```
Copytsx
<ColorWheel
getValueLabel={color =>
color
.toFormat("hsl")
.withChannelValue("saturation", 100)
.withChannelValue("lightness", 50)
.withChannelValue("alpha", 1)
.toString()
}
>
<ColorWheel.Label>Label</ColorWheel.Label>
<ColorWheel.Track>
<ColorWheel.Thumb>
<ColorWheel.Input />
</ColorWheel.Thumb>
</ColorWheel.Track>
<div>
<ColorWheel.ValueLabel />
</div>
</ColorWheel>
```
### HTML forms
`ColorWheel` supports the `name` prop for integration with HTML forms.
Label
ResetSubmit
```
Copytsx
function HTMLFormExample() {
const onSubmit = (e: SubmitEvent) => {
// handle form submission.
};
return (
<form onSubmit={onSubmit}>
<ColorWheel name="hue">
<ColorWheel.Track>
<ColorWheel.Thumb>
<ColorWheel.Input />
</ColorWheel.Thumb>
</ColorWheel.Track>
</ColorWheel>
</form>
);
}
```
```
Copytsx
function HTMLFormExample() {
const onSubmit = (e: SubmitEvent) => {
// handle form submission.
};
return (
<form onSubmit={onSubmit}>
<ColorWheel name="hue">
<ColorWheel.Track>
<ColorWheel.Thumb>
<ColorWheel.Input />
</ColorWheel.Thumb>
</ColorWheel.Track>
</ColorWheel>
</form>
);
}
```
## API Reference
### ColorWheel
`ColorWheel` is equivalent to the `Root` import from `@kobalte/core/color-wheel`.
| Prop | Description |
| --- | --- |
| value | `Color`<br> The controlled value of the color wheel. Must be used in conjunction with `onChange`. |
| defaultValue | `Color`<br> The value of the color wheel when initially rendered. Use when you do not need to control the state of the color wheel. |
| thickness | `number`<br> The thickness of the track `0-100`. |
| onChange | `(value: Color) => void`<br> Event handler called when the value changes. |
| onChangeEnd | `(value: Color) => void`<br> Event handler called when the value changes at the end of an interaction. |
| getValueLabel | `(param: Color) => string`<br> A function to get the accessible label text representing the current value in a human-readable format. |
| name | `string`<br> The name of the color wheel, used when submitting an HTML form. |
| validationState | `'valid' | 'invalid'`<br> Whether the color wheel should display its "valid" or "invalid" visual styling. |
| required | `boolean`<br> Whether the user must check a radio group item before the owning form can be submitted. |
| disabled | `boolean`<br> Whether the color wheel is disabled. |
| readOnly | `boolean`<br> Whether the color wheel items can be selected but not changed by the user. |
| translations | `ColorIntlTranslations`<br> Localization strings. |
| Data attribute | Description |
| --- | --- |
| data-valid | Present when the color wheel is valid according to the validation rules. |
| data-invalid | Present when the color wheel is invalid according to the validation rules. |
| data-required | Present when the color wheel is required. |
| data-disabled | Present when the color wheel is disabled. |
| data-readonly | Present when the color wheel is read only. |
`ColorWheel.ValueLabel`, `ColorWheel.Input`, `ColorWheel.Thumb` and `ColorWheel.Track` share the same data-attributes.
### ColorWheel.Thumb
The current color is available on the thumb using the custom css property `--kb-color-current`.
## Rendered elements
| Component | Default rendered element |
| --- | --- |
| `ColorWheel` | `div` |
| `ColorWheel.Track` | `div` |
| `ColorWheel.Thumb` | `span` |
| `ColorWheel.Input` | `input` |
| `ColorWheel.ValueLabel` | `div` |
| `ColorWheel.Label` | `label` |
| `ColorWheel.Description` | `div` |
| `ColorWheel.ErrorMessage` | `div` |
## Accessibility
### Keyboard Interactions
| Key | Description |
| --- | --- |
| `PageUp` | Increments the value of the thumb by a larger step. |
| `PageDown` | Decrements the value of the thumb by a larger step. |
| `ArrowDown` | Decrements the value of the thumb by the step amount. |
| `ArrowUp` | Increments the value of the thumb by the step amount. |
| `ArrowRight` | Increments the value of the thumb by the step value. |
| `ArrowLeft` | Decrements the value of the thumb by the step value. |
| `Home` | Sets the value of the thumb to the minimum value. |
| `End` | Sets the value of the thumb to the maximum value. |
Previous[←Color Swatch](https://kobalte.dev/docs/core/components/color-swatch)Next[Combobox→](https://kobalte.dev/docs/core/components/combobox)

View File

@ -0,0 +1,669 @@
# Context Menu
Displays a menu located at the pointer, triggered by a right-click or a long-press.
## Import
```
Copyts
import { ContextMenu } from "@kobalte/core/context-menu";
// or
import { Root, Trigger, ... } from "@kobalte/core/context-menu";
// or (deprecated)
import { ContextMenu } from "@kobalte/core";
```
```
Copyts
import { ContextMenu } from "@kobalte/core/context-menu";
// or
import { Root, Trigger, ... } from "@kobalte/core/context-menu";
// or (deprecated)
import { ContextMenu } from "@kobalte/core";
```
## Features
- Follows the [WAI ARIA Menu](https://www.w3.org/WAI/ARIA/apg/patterns/menu/) design pattern.
- Triggers with a long-press on touch devices.
- Supports modal and non-modal modes.
- Supports submenus.
- Supports items, labels, groups of items.
- Supports checkable items (single or multiple) with optional indeterminate state.
- Support disabled items.
- Complex item labeling support for accessibility.
- Keyboard opening and navigation support.
- Automatic scrolling support during keyboard navigation.
- Typeahead to allow focusing items by typing text.
- Optionally render a pointing arrow.
- Focus is fully managed.
## Anatomy
The context menu consists of:
- **ContextMenu:** The root container for a context menu.
- **ContextMenu.Trigger:** The button that toggles the menu.
- **ContextMenu.Icon:** A small icon that can be displayed inside the menu trigger as a visual affordance for the fact it can be open.
- **ContextMenu.Portal:** Portals its children into the `body` when the menu is open.
- **ContextMenu.Content:** Contains the content to be rendered when the menu is open.
- **ContextMenu.Arrow:** An optional arrow element to render alongside the menu content.
- **ContextMenu.Separator:** Used to visually separate items in the menu.
- **ContextMenu.Group:** Used to group multiple items. Use in conjunction with `ContextMenu.GroupLabel` to ensure good accessibility via automatic labelling.
- **ContextMenu.GroupLabel:** Used to render the label of a group. It won't be focusable using arrow keys.
- **ContextMenu.Sub:** Contains all the parts of a submenu.
- **ContextMenu.SubTrigger:** An item that opens a submenu. Must be rendered inside `ContextMenu.Sub`.
- **ContextMenu.SubContent:** The component that pops out when a submenu is open. Must be rendered inside `ContextMenu.Sub`.
The menu item consists of:
- **ContextMenu.Item:** An item of the select.
- **ContextMenu.ItemLabel:** An accessible label to be announced for the item.
- **ContextMenu.ItemDescription:** An optional accessible description to be announced for the item.
- **ContextMenu.ItemIndicator:** The visual indicator rendered when the item is checked.
The checkable menu item consists of:
- **ContextMenu.RadioGroup:** Used to group multiple `ContextMenu.RadioItem`s and manage the selection.
- **ContextMenu.RadioItem:** An item that can be controlled and rendered like a radio.
- **ContextMenu.CheckboxItem:** An item that can be controlled and rendered like a checkbox.
```
Copytsx
<ContextMenu>
<ContextMenu.Trigger>
<ContextMenu.Icon />
</ContextMenu.Trigger>
<ContextMenu.Portal>
<ContextMenu.Content>
<ContextMenu.Arrow />
<ContextMenu.Item>
<ContextMenu.ItemLabel />
<ContextMenu.ItemDescription />
</ContextMenu.Item>
<ContextMenu.Group>
<ContextMenu.GroupLabel />
<ContextMenu.Item />
</ContextMenu.Group>
<ContextMenu.CheckboxItem>
<ContextMenu.ItemIndicator />
</ContextMenu.CheckboxItem>
<ContextMenu.RadioGroup>
<ContextMenu.RadioItem>
<ContextMenu.ItemIndicator />
</ContextMenu.RadioItem>
</ContextMenu.RadioGroup>
<ContextMenu.Sub>
<ContextMenu.SubTrigger />
<ContextMenu.Portal>
<ContextMenu.SubContent />
</ContextMenu.Portal>
</ContextMenu.Sub>
<ContextMenu.Separator />
</ContextMenu.Content>
</ContextMenu.Portal>
</ContextMenu>
```
```
Copytsx
<ContextMenu>
<ContextMenu.Trigger>
<ContextMenu.Icon />
</ContextMenu.Trigger>
<ContextMenu.Portal>
<ContextMenu.Content>
<ContextMenu.Arrow />
<ContextMenu.Item>
<ContextMenu.ItemLabel />
<ContextMenu.ItemDescription />
</ContextMenu.Item>
<ContextMenu.Group>
<ContextMenu.GroupLabel />
<ContextMenu.Item />
</ContextMenu.Group>
<ContextMenu.CheckboxItem>
<ContextMenu.ItemIndicator />
</ContextMenu.CheckboxItem>
<ContextMenu.RadioGroup>
<ContextMenu.RadioItem>
<ContextMenu.ItemIndicator />
</ContextMenu.RadioItem>
</ContextMenu.RadioGroup>
<ContextMenu.Sub>
<ContextMenu.SubTrigger />
<ContextMenu.Portal>
<ContextMenu.SubContent />
</ContextMenu.Portal>
</ContextMenu.Sub>
<ContextMenu.Separator />
</ContextMenu.Content>
</ContextMenu.Portal>
</ContextMenu>
```
## Example
Right click here.
index.tsxstyle.css
```
Copytsx
import { ContextMenu } from "@kobalte/core/context-menu";
import { createSignal } from "solid-js";
import { CheckIcon, ChevronRightIcon, DotFilledIcon } from "some-icon-library";
import "./style.css";
function App() {
const [showGitLog, setShowGitLog] = createSignal(true);
const [showHistory, setShowHistory] = createSignal(false);
const [branch, setBranch] = createSignal("main");
return (
<ContextMenu>
<ContextMenu.Trigger class="context-menu__trigger">
Right click here.
</ContextMenu.Trigger>
<ContextMenu.Portal>
<ContextMenu.Content class="context-menu__content">
<ContextMenu.Item class="context-menu__item">
Commit <div class="context-menu__item-right-slot">⌘+K</div>
</ContextMenu.Item>
<ContextMenu.Item class="context-menu__item">
Push <div class="context-menu__item-right-slot">⇧+⌘+K</div>
</ContextMenu.Item>
<ContextMenu.Item class="context-menu__item" disabled>
Update Project <div class="context-menu__item-right-slot">⌘+T</div>
</ContextMenu.Item>
<ContextMenu.Sub overlap gutter={4} shift={-8}>
<ContextMenu.SubTrigger class="context-menu__sub-trigger">
GitHub
<div class="context-menu__item-right-slot">
<ChevronRightIcon width={20} height={20} />
</div>
</ContextMenu.SubTrigger>
<ContextMenu.Portal>
<ContextMenu.SubContent class="context-menu__sub-content">
<ContextMenu.Item class="context-menu__item">
Create Pull Request…
</ContextMenu.Item>
<ContextMenu.Item class="context-menu__item">
View Pull Requests
</ContextMenu.Item>
<ContextMenu.Item class="context-menu__item">
Sync Fork
</ContextMenu.Item>
<ContextMenu.Separator class="context-menu__separator" />
<ContextMenu.Item class="context-menu__item">
Open on GitHub
</ContextMenu.Item>
</ContextMenu.SubContent>
</ContextMenu.Portal>
</ContextMenu.Sub>
<ContextMenu.Separator class="context-menu__separator" />
<ContextMenu.CheckboxItem
class="context-menu__checkbox-item"
checked={showGitLog()}
onChange={setShowGitLog}
>
<ContextMenu.ItemIndicator class="context-menu__item-indicator">
<CheckIcon />
</ContextMenu.ItemIndicator>
Show Git Log
</ContextMenu.CheckboxItem>
<ContextMenu.CheckboxItem
class="context-menu__checkbox-item"
checked={showHistory()}
onChange={setShowHistory}
>
<ContextMenu.ItemIndicator class="context-menu__item-indicator">
<CheckIcon />
</ContextMenu.ItemIndicator>
Show History
</ContextMenu.CheckboxItem>
<ContextMenu.Separator class="context-menu__separator" />
<ContextMenu.Group>
<ContextMenu.GroupLabel class="context-menu__group-label">
Branches
</ContextMenu.GroupLabel>
<ContextMenu.RadioGroup value={branch()} onChange={setBranch}>
<ContextMenu.RadioItem class="context-menu__radio-item" value="main">
<ContextMenu.ItemIndicator class="context-menu__item-indicator">
<DotFilledIcon />
</ContextMenu.ItemIndicator>
main
</ContextMenu.RadioItem>
<ContextMenu.RadioItem class="context-menu__radio-item" value="develop">
<ContextMenu.ItemIndicator class="context-menu__item-indicator">
<DotFilledIcon />
</ContextMenu.ItemIndicator>
develop
</ContextMenu.RadioItem>
</ContextMenu.RadioGroup>
</ContextMenu.Group>
</ContextMenu.Content>
</ContextMenu.Portal>
</ContextMenu>
);
}
```
```
Copytsx
import { ContextMenu } from "@kobalte/core/context-menu";
import { createSignal } from "solid-js";
import { CheckIcon, ChevronRightIcon, DotFilledIcon } from "some-icon-library";
import "./style.css";
function App() {
const [showGitLog, setShowGitLog] = createSignal(true);
const [showHistory, setShowHistory] = createSignal(false);
const [branch, setBranch] = createSignal("main");
return (
<ContextMenu>
<ContextMenu.Trigger class="context-menu__trigger">
Right click here.
</ContextMenu.Trigger>
<ContextMenu.Portal>
<ContextMenu.Content class="context-menu__content">
<ContextMenu.Item class="context-menu__item">
Commit <div class="context-menu__item-right-slot">⌘+K</div>
</ContextMenu.Item>
<ContextMenu.Item class="context-menu__item">
Push <div class="context-menu__item-right-slot">⇧+⌘+K</div>
</ContextMenu.Item>
<ContextMenu.Item class="context-menu__item" disabled>
Update Project <div class="context-menu__item-right-slot">⌘+T</div>
</ContextMenu.Item>
<ContextMenu.Sub overlap gutter={4} shift={-8}>
<ContextMenu.SubTrigger class="context-menu__sub-trigger">
GitHub
<div class="context-menu__item-right-slot">
<ChevronRightIcon width={20} height={20} />
</div>
</ContextMenu.SubTrigger>
<ContextMenu.Portal>
<ContextMenu.SubContent class="context-menu__sub-content">
<ContextMenu.Item class="context-menu__item">
Create Pull Request…
</ContextMenu.Item>
<ContextMenu.Item class="context-menu__item">
View Pull Requests
</ContextMenu.Item>
<ContextMenu.Item class="context-menu__item">
Sync Fork
</ContextMenu.Item>
<ContextMenu.Separator class="context-menu__separator" />
<ContextMenu.Item class="context-menu__item">
Open on GitHub
</ContextMenu.Item>
</ContextMenu.SubContent>
</ContextMenu.Portal>
</ContextMenu.Sub>
<ContextMenu.Separator class="context-menu__separator" />
<ContextMenu.CheckboxItem
class="context-menu__checkbox-item"
checked={showGitLog()}
onChange={setShowGitLog}
>
<ContextMenu.ItemIndicator class="context-menu__item-indicator">
<CheckIcon />
</ContextMenu.ItemIndicator>
Show Git Log
</ContextMenu.CheckboxItem>
<ContextMenu.CheckboxItem
class="context-menu__checkbox-item"
checked={showHistory()}
onChange={setShowHistory}
>
<ContextMenu.ItemIndicator class="context-menu__item-indicator">
<CheckIcon />
</ContextMenu.ItemIndicator>
Show History
</ContextMenu.CheckboxItem>
<ContextMenu.Separator class="context-menu__separator" />
<ContextMenu.Group>
<ContextMenu.GroupLabel class="context-menu__group-label">
Branches
</ContextMenu.GroupLabel>
<ContextMenu.RadioGroup value={branch()} onChange={setBranch}>
<ContextMenu.RadioItem class="context-menu__radio-item" value="main">
<ContextMenu.ItemIndicator class="context-menu__item-indicator">
<DotFilledIcon />
</ContextMenu.ItemIndicator>
main
</ContextMenu.RadioItem>
<ContextMenu.RadioItem class="context-menu__radio-item" value="develop">
<ContextMenu.ItemIndicator class="context-menu__item-indicator">
<DotFilledIcon />
</ContextMenu.ItemIndicator>
develop
</ContextMenu.RadioItem>
</ContextMenu.RadioGroup>
</ContextMenu.Group>
</ContextMenu.Content>
</ContextMenu.Portal>
</ContextMenu>
);
}
```
## Usage
### Origin-aware animations
We expose a CSS custom property `--kb-popper-content-transform-origin` which can be used to animate the content from its computed origin.
```
Copycss
/* style.css */
.context-menu__content,
.context-menu__sub-content {
transform-origin: var(--kb-menu-content-transform-origin);
animation: contentHide 250ms ease-in forwards;
}
.context-menu__content[data-expanded],
.context-menu__sub-content[data-expanded] {
animation: contentShow 250ms ease-out;
}
@keyframes contentShow {
from {
opacity: 0;
transform: scale(0.96);
}
to {
opacity: 1;
transform: scale(1);
}
}
@keyframes contentHide {
from {
opacity: 1;
transform: scale(1);
}
to {
opacity: 0;
transform: scale(0.96);
}
}
```
```
Copycss
/* style.css */
.context-menu__content,
.context-menu__sub-content {
transform-origin: var(--kb-menu-content-transform-origin);
animation: contentHide 250ms ease-in forwards;
}
.context-menu__content[data-expanded],
.context-menu__sub-content[data-expanded] {
animation: contentShow 250ms ease-out;
}
@keyframes contentShow {
from {
opacity: 0;
transform: scale(0.96);
}
to {
opacity: 1;
transform: scale(1);
}
}
@keyframes contentHide {
from {
opacity: 1;
transform: scale(1);
}
to {
opacity: 0;
transform: scale(0.96);
}
}
```
## API Reference
### ContextMenu
`ContextMenu` is equivalent to the `Root` import from `@kobalte/core/context-menu` (and deprecated `ContextMenu.Root`).
| Prop | Description |
| --- | --- |
| onOpenChange | `(open: boolean) => void`<br> Event handler called when the open state of the menu changes. |
| id | `string`<br> A unique identifier for the component. The id is used to generate id attributes for nested components. If no id prop is provided, a generated id will be used. |
| modal | `boolean`<br> Whether the menu should be the only visible content for screen readers, when set to `true`: <br> \- interaction with outside elements will be disabled. <br> \- scroll will be locked. <br> \- focus will be locked inside the menu content. <br> \- elements outside the menu content will not be visible for screen readers. |
| preventScroll | `boolean`<br> Whether the scroll should be locked even if the menu is not modal. |
| forceMount | `boolean`<br> Used to force mounting the menu (portal and content) when more control is needed. Useful when controlling animation with SolidJS animation libraries. |
`ContextMenu` also accepts the following props to customize the placement of the `ContextMenu.Content`.
| Prop | Description |
| --- | --- |
| placement | `Placement`<br> The placement of the menu content. |
| gutter | `number`<br> The distance between the menu content and the trigger element. By default, it's 0 plus half of the arrow offset, if it exists. |
| shift | `number`<br> The skidding of the menu content along the trigger element. |
| flip | `boolean | string`<br> Controls the behavior of the menu content when it overflows the viewport:<br> \- If a `boolean`, specifies whether the menu content should flip to the opposite side when it overflows.<br> \- If a `string`, indicates the preferred fallback placements when it overflows.<br>The placements must be spaced-delimited, e.g. "top left". |
| slide | `boolean`<br> Whether the menu content should slide when it overflows. |
| overlap | `boolean`<br> Whether the menu content can overlap the trigger element when it overflows. |
| sameWidth | `boolean`<br> Whether the menu content should have the same width as the trigger element. This will be exposed to CSS as `--kb-popper-anchor-width`. |
| fitViewport | `boolean`<br> Whether the menu content should fit the viewport. If this is set to true, the menu content will have `maxWidth` and `maxHeight` set to the viewport size. This will be exposed to CSS as `--kb-popper-available-width` and `--kb-popper-available-height`. |
| hideWhenDetached | `boolean`<br> Whether to hide the menu content when the trigger element becomes occluded. |
| detachedPadding | `number`<br> The minimum padding in order to consider the trigger element occluded. |
| arrowPadding | `number`<br> The minimum padding between the arrow and the menu content corner. |
| overflowPadding | `number`<br> The minimum padding between the menu content and the viewport edge. This will be exposed to CSS as `--kb-popper-overflow-padding`. |
### ContextMenu.Trigger
| Prop | Description |
| --- | --- |
| disabled | `boolean`<br> Whether the context menu trigger is disabled or not. |
| Data attribute | Description |
| --- | --- |
| data-expanded | Present when the menu is open. |
| data-closed | Present when the menu is close. |
| data-disabled | Present when the trigger is disabled. |
`ContextMenu.Icon`, `ContextMenu.Content`, `ContextMenu.SubTrigger` and `ContextMenu.SubContent` share the same `data-expanded` attribute.
### ContextMenu.Content
The popper positioner will copy the same `z-index` as the `ContextMenu.Content`.
| Prop | Description |
| --- | --- |
| onOpenAutoFocus | `(event: Event) => void`<br> Event handler called when focus moves into the component after opening. It can be prevented by calling `event.preventDefault`. |
| onCloseAutoFocus | `(event: Event) => void`<br> Event handler called when focus moves to the trigger after closing. It can be prevented by calling `event.preventDefault`. |
| onEscapeKeyDown | `(event: KeyboardEvent) => void`<br> Event handler called when the escape key is down. It can be prevented by calling `event.preventDefault`. |
| onPointerDownOutside | `(event: PointerDownOutsideEvent) => void`<br> Event handler called when a pointer event occurs outside the bounds of the component. It can be prevented by calling `event.preventDefault`. |
| onFocusOutside | `(event: FocusOutsideEvent) => void`<br> Event handler called when the focus moves outside the bounds of the component. It can be prevented by calling `event.preventDefault`. |
| onInteractOutside | `(event: InteractOutsideEvent) => void`<br> Event handler called when an interaction (pointer or focus event) happens outside the bounds of the component. It can be prevented by calling `event.preventDefault`. |
### ContextMenu.Arrow
| Prop | Description |
| --- | --- |
| size | `number`<br> The size of the arrow. |
### ContextMenu.Item
| Prop | Description |
| --- | --- |
| textValue | `string`<br> Optional text used for typeahead purposes. By default, the typeahead behavior will use the .textContent of the `ContextMenu.ItemLabel` part if provided, or fallback to the .textContent of the `ContextMenu.Item`. Use this when the content is complex, or you have non-textual content inside. |
| disabled | `boolean`<br> Whether the item is disabled or not. |
| closeOnSelect | `boolean`<br> Whether the menu should close when the item is activated. |
| onSelect | `() => void`<br> Event handler called when the user selects an item (via mouse or keyboard). |
| Data attribute | Description |
| --- | --- |
| data-disabled | Present when the item is disabled. |
| data-highlighted | Present when the item is highlighted. |
`ContextMenu.ItemLabel`, `ContextMenu.ItemDescription` and `ContextMenu.ItemIndicator` shares the same data-attributes.
### ContextMenu.ItemIndicator
| Prop | Description |
| --- | --- |
| forceMount | `boolean`<br> Used to force mounting when more control is needed. Useful when controlling animation with SolidJS animation libraries. |
### ContextMenu.RadioGroup
| Prop | Description |
| --- | --- |
| value | `string`<br> The controlled value of the menu radio item to check. |
| defaultValue | `string`<br> The value of the menu radio item that should be checked when initially rendered. Useful when you do not need to control the state of the radio group. |
| onChange | `(value: string) => void`<br> Event handler called when the value changes. |
| disabled | `boolean`<br> Whether the radio group is disabled or not. |
### ContextMenu.RadioItem
| Prop | Description |
| --- | --- |
| value | `string`<br> The value of the menu item radio. |
| textValue | `string`<br> Optional text used for typeahead purposes. By default, the typeahead behavior will use the .textContent of the `ContextMenu.ItemLabel` part if provided, or fallback to the .textContent of the `ContextMenu.Item`. Use this when the content is complex, or you have non-textual content inside. |
| disabled | `boolean`<br> Whether the item is disabled or not. |
| closeOnSelect | `boolean`<br> Whether the menu should close when the item is checked. |
| onSelect | `() => void`<br> Event handler called when the user selects an item (via mouse or keyboard). |
| Data attribute | Description |
| --- | --- |
| data-disabled | Present when the item is disabled. |
| data-checked | Present when the item is checked. |
| data-highlighted | Present when the item is highlighted. |
### ContextMenu.CheckboxItem
| Prop | Description |
| --- | --- |
| checked | `boolean`<br> The controlled checked state of the item. |
| defaultChecked | `boolean`<br> The default checked state when initially rendered. Useful when you do not need to control the checked state. |
| onChange | `(checked: boolean) => void`<br> Event handler called when the checked state of the item changes. |
| textValue | `string`<br> Optional text used for typeahead purposes. By default, the typeahead behavior will use the .textContent of the `ContextMenu.ItemLabel` part if provided, or fallback to the .textContent of the `ContextMenu.Item`. Use this when the content is complex, or you have non-textual content inside. |
| indeterminate | `boolean`<br> Whether the item is in an indeterminate state. |
| disabled | `boolean`<br> Whether the item is disabled or not. |
| closeOnSelect | `boolean`<br> Whether the menu should close when the item is checked/unchecked. |
| onSelect | `() => void`<br> Event handler called when the user selects an item (via mouse or keyboard). |
| Data attribute | Description |
| --- | --- |
| data-disabled | Present when the item is disabled. |
| data-indeterminate | Present when the item is in an indeterminate state. |
| data-checked | Present when the item is checked. |
| data-highlighted | Present when the item is highlighted. |
### ContextMenu.Sub
| Prop | Description |
| --- | --- |
| open | `boolean`<br> The controlled open state of the sub menu. |
| defaultOpen | `boolean`<br> The default open state when initially rendered. Useful when you do not need to control the open state. |
| onOpenChange | `(open: boolean) => void`<br> Event handler called when the open state of the sub menu changes. |
`ContextMenu.Sub` also accepts the following props to customize the placement of the `ContextMenu.SubContent`.
| Prop | Description |
| --- | --- |
| getAnchorRect | `(anchor?: HTMLElement) => AnchorRect | undefined`<br> Function that returns the trigger element's DOMRect. |
| gutter | `number`<br> The distance between the sub menu content and the trigger element. By default, it's 0 plus half of the arrow offset, if it exists. |
| shift | `number`<br> The skidding of the sub menu content along the trigger element. |
| slide | `boolean`<br> Whether the sub menu content should slide when it overflows. |
| overlap | `boolean`<br> Whether the sub menu content can overlap the trigger element when it overflows. |
| fitViewport | `boolean`<br> Whether the sub menu content should fit the viewport. If this is set to true, the sub menu content will have `maxWidth` and `maxHeight` set to the viewport size. This will be exposed to CSS as `--kb-popper-available-width` and `--kb-popper-available-height`. |
| hideWhenDetached | `boolean`<br> Whether to hide the sub menu content when the trigger element becomes occluded. |
| detachedPadding | `number`<br> The minimum padding in order to consider the trigger element occluded. |
| arrowPadding | `number`<br> The minimum padding between the arrow and the sub menu content corner. |
| overflowPadding | `number`<br> The minimum padding between the sub menu content and the viewport edge. This will be exposed to CSS as `--kb-popper-overflow-padding`. |
### ContextMenu.SubTrigger
| Prop | Description |
| --- | --- |
| textValue | `string`<br> Optional text used for typeahead purposes. By default, the typeahead behavior will use the .textContent of the `ContextMenu.SubTrigger`. Use this when the content is complex, or you have non-textual content inside. |
| disabled | `boolean`<br> Whether the sub menu trigger is disabled or not. |
| Data attribute | Description |
| --- | --- |
| data-disabled | Present when the item is disabled. |
| data-highlighted | Present when the item is highlighted. |
### ContextMenu.SubContent
| Prop | Description |
| --- | --- |
| onEscapeKeyDown | `(event: KeyboardEvent) => void`<br> Event handler called when the escape key is down. It can be prevented by calling `event.preventDefault`. |
| onPointerDownOutside | `(event: PointerDownOutsideEvent) => void`<br> Event handler called when a pointer event occurs outside the bounds of the component. It can be prevented by calling `event.preventDefault`. |
| onFocusOutside | `(event: FocusOutsideEvent) => void`<br> Event handler called when the focus moves outside the bounds of the component. It can be prevented by calling `event.preventDefault`. |
| onInteractOutside | `(event: InteractOutsideEvent) => void`<br> Event handler called when an interaction (pointer or focus event) happens outside the bounds of the component. It can be prevented by calling `event.preventDefault`. |
## Rendered elements
| Component | Default rendered element |
| --- | --- |
| `ContextMenu` | none |
| `ContextMenu.Trigger` | `div` |
| `ContextMenu.Icon` | `div` |
| `ContextMenu.Portal` | `Portal` |
| `ContextMenu.Content` | `div` |
| `ContextMenu.Arrow` | `div` |
| `ContextMenu.Separator` | `hr` |
| `ContextMenu.Group` | `div` |
| `ContextMenu.GroupLabel` | `span` |
| `ContextMenu.Sub` | none |
| `ContextMenu.SubTrigger` | `div` |
| `ContextMenu.SubContent` | `div` |
| `ContextMenu.Item` | `div` |
| `ContextMenu.ItemLabel` | `div` |
| `ContextMenu.ItemDescription` | `div` |
| `ContextMenu.ItemIndicator` | `div` |
| `ContextMenu.RadioGroup` | `div` |
| `ContextMenu.RadioItem` | `div` |
| `ContextMenu.CheckboxItem` | `div` |
## Accessibility
### Keyboard Interactions
| Key | Description |
| --- | --- |
| `Space` | When focus is on an item, activates the item. |
| `Enter` | When focus is on an item, activates the item. |
| `ArrowDown` | When focus is on an item, moves focus to the next item. |
| `ArrowUp` | When focus is on an item, moves focus to the previous item. |
| `ArrowRight` / `ArrowLeft` | When focus is on a sub menu trigger, opens or closes the submenu depending on reading direction. |
| `Home` | When focus is on an item, moves focus to first item. |
| `End` | When focus is on an item, moves focus to last item. |
| `Esc` | Closes the context menu. |
Previous[←Combobox](https://kobalte.dev/docs/core/components/combobox)Next[Dialog→](https://kobalte.dev/docs/core/components/dialog)

View File

@ -0,0 +1,735 @@
# Dropdown Menu
Displays a menu to the user —such as a set of actions or functions— triggered by a button.
## Import
```
Copyts
import { DropdownMenu } from "@kobalte/core/dropdown-menu";
// or
import { Root, Trigger, ... } from "@kobalte/core/dropdown-menu";
// or (deprecated)
import { DropdownMenu } from "@kobalte/core";
```
```
Copyts
import { DropdownMenu } from "@kobalte/core/dropdown-menu";
// or
import { Root, Trigger, ... } from "@kobalte/core/dropdown-menu";
// or (deprecated)
import { DropdownMenu } from "@kobalte/core";
```
## Features
- Exposed to assistive technology as a button with a menu using the [WAI ARIA Menu Button](https://www.w3.org/WAI/ARIA/apg/patterns/menubutton/) design pattern.
- Supports modal and non-modal modes.
- Supports submenus.
- Supports items, labels, groups of items.
- Supports checkable items (single or multiple) with optional indeterminate state.
- Support disabled items.
- Complex item labeling support for accessibility.
- Keyboard opening and navigation support.
- Automatic scrolling support during keyboard navigation.
- Typeahead to allow focusing items by typing text.
- Optionally render a pointing arrow.
- Focus is fully managed.
- Can be controlled or uncontrolled.
## Anatomy
The dropdown menu consists of:
- **DropdownMenu:** The root container for a dropdown menu.
- **DropdownMenu.Trigger:** The button that toggles the menu.
- **DropdownMenu.Icon:** A small icon often displayed next to the menu trigger as a visual affordance for the fact it can be open.
- **DropdownMenu.Portal:** Portals its children into the `body` when the menu is open.
- **DropdownMenu.Content:** Contains the content to be rendered when the menu is open.
- **DropdownMenu.Arrow:** An optional arrow element to render alongside the menu content.
- **DropdownMenu.Separator:** Used to visually separate items in the menu.
- **DropdownMenu.Group:** Used to group multiple items. Use in conjunction with `DropdownMenu.GroupLabel` to ensure good accessibility via automatic labelling.
- **DropdownMenu.GroupLabel:** Used to render the label of a group. It won't be focusable using arrow keys.
- **DropdownMenu.Sub:** Contains all the parts of a submenu.
- **DropdownMenu.SubTrigger:** An item that opens a submenu. Must be rendered inside `DropdownMenu.Sub`.
- **DropdownMenu.SubContent:** The component that pops out when a submenu is open. Must be rendered inside `DropdownMenu.Sub`.
The menu item consists of:
- **DropdownMenu.Item:** An item of the select.
- **DropdownMenu.ItemLabel:** An accessible label to be announced for the item.
- **DropdownMenu.ItemDescription:** An optional accessible description to be announced for the item.
- **DropdownMenu.ItemIndicator:** The visual indicator rendered when the item is checked.
The checkable menu item consists of:
- **DropdownMenu.RadioGroup:** Used to group multiple `DropdownMenu.RadioItem`s and manage the selection.
- **DropdownMenu.RadioItem:** An item that can be controlled and rendered like a radio.
- **DropdownMenu.CheckboxItem:** An item that can be controlled and rendered like a checkbox.
```
Copytsx
<DropdownMenu>
<DropdownMenu.Trigger>
<DropdownMenu.Icon />
</DropdownMenu.Trigger>
<DropdownMenu.Portal>
<DropdownMenu.Content>
<DropdownMenu.Arrow />
<DropdownMenu.Item>
<DropdownMenu.ItemLabel />
<DropdownMenu.ItemDescription />
</DropdownMenu.Item>
<DropdownMenu.Group>
<DropdownMenu.GroupLabel />
<DropdownMenu.Item />
</DropdownMenu.Group>
<DropdownMenu.CheckboxItem>
<DropdownMenu.ItemIndicator />
</DropdownMenu.CheckboxItem>
<DropdownMenu.RadioGroup>
<DropdownMenu.RadioItem>
<DropdownMenu.ItemIndicator />
</DropdownMenu.RadioItem>
</DropdownMenu.RadioGroup>
<DropdownMenu.Sub>
<DropdownMenu.SubTrigger />
<DropdownMenu.Portal>
<DropdownMenu.SubContent />
</DropdownMenu.Portal>
</DropdownMenu.Sub>
<DropdownMenu.Separator />
</DropdownMenu.Content>
</DropdownMenu.Portal>
</DropdownMenu>
```
```
Copytsx
<DropdownMenu>
<DropdownMenu.Trigger>
<DropdownMenu.Icon />
</DropdownMenu.Trigger>
<DropdownMenu.Portal>
<DropdownMenu.Content>
<DropdownMenu.Arrow />
<DropdownMenu.Item>
<DropdownMenu.ItemLabel />
<DropdownMenu.ItemDescription />
</DropdownMenu.Item>
<DropdownMenu.Group>
<DropdownMenu.GroupLabel />
<DropdownMenu.Item />
</DropdownMenu.Group>
<DropdownMenu.CheckboxItem>
<DropdownMenu.ItemIndicator />
</DropdownMenu.CheckboxItem>
<DropdownMenu.RadioGroup>
<DropdownMenu.RadioItem>
<DropdownMenu.ItemIndicator />
</DropdownMenu.RadioItem>
</DropdownMenu.RadioGroup>
<DropdownMenu.Sub>
<DropdownMenu.SubTrigger />
<DropdownMenu.Portal>
<DropdownMenu.SubContent />
</DropdownMenu.Portal>
</DropdownMenu.Sub>
<DropdownMenu.Separator />
</DropdownMenu.Content>
</DropdownMenu.Portal>
</DropdownMenu>
```
## Example
Git SettingsChevron
index.tsxstyle.css
```
Copytsx
import { DropdownMenu } from "@kobalte/core/dropdown-menu";
import { createSignal } from "solid-js";
import { CheckIcon, ChevronDownIcon, ChevronRightIcon, DotFilledIcon } from "some-icon-library";
import "./style.css";
function App() {
const [showGitLog, setShowGitLog] = createSignal(true);
const [showHistory, setShowHistory] = createSignal(false);
const [branch, setBranch] = createSignal("main");
return (
<DropdownMenu>
<DropdownMenu.Trigger class="dropdown-menu__trigger">
<span>Git Settings</span>
<DropdownMenu.Icon class="dropdown-menu__trigger-icon">
<ChevronDownIcon />
</DropdownMenu.Icon>
</DropdownMenu.Trigger>
<DropdownMenu.Portal>
<DropdownMenu.Content class="dropdown-menu__content">
<DropdownMenu.Item class="dropdown-menu__item">
Commit <div class="dropdown-menu__item-right-slot">⌘+K</div>
</DropdownMenu.Item>
<DropdownMenu.Item class="dropdown-menu__item">
Push <div class="dropdown-menu__item-right-slot">⇧+⌘+K</div>
</DropdownMenu.Item>
<DropdownMenu.Item class="dropdown-menu__item" disabled>
Update Project <div class="dropdown-menu__item-right-slot">⌘+T</div>
</DropdownMenu.Item>
<DropdownMenu.Sub overlap gutter={4} shift={-8}>
<DropdownMenu.SubTrigger class="dropdown-menu__sub-trigger">
GitHub
<div class="dropdown-menu__item-right-slot">
<ChevronRightIcon width={20} height={20} />
</div>
</DropdownMenu.SubTrigger>
<DropdownMenu.Portal>
<DropdownMenu.SubContent class="dropdown-menu__sub-content">
<DropdownMenu.Item class="dropdown-menu__item">
Create Pull Request…
</DropdownMenu.Item>
<DropdownMenu.Item class="dropdown-menu__item">
View Pull Requests
</DropdownMenu.Item>
<DropdownMenu.Item class="dropdown-menu__item">
Sync Fork
</DropdownMenu.Item>
<DropdownMenu.Separator class="dropdown-menu__separator" />
<DropdownMenu.Item class="dropdown-menu__item">
Open on GitHub
</DropdownMenu.Item>
</DropdownMenu.SubContent>
</DropdownMenu.Portal>
</DropdownMenu.Sub>
<DropdownMenu.Separator class="dropdown-menu__separator" />
<DropdownMenu.CheckboxItem
class="dropdown-menu__checkbox-item"
checked={showGitLog()}
onChange={setShowGitLog}
>
<DropdownMenu.ItemIndicator class="dropdown-menu__item-indicator">
<CheckIcon />
</DropdownMenu.ItemIndicator>
Show Git Log
</DropdownMenu.CheckboxItem>
<DropdownMenu.CheckboxItem
class="dropdown-menu__checkbox-item"
checked={showHistory()}
onChange={setShowHistory}
>
<DropdownMenu.ItemIndicator class="dropdown-menu__item-indicator">
<CheckIcon />
</DropdownMenu.ItemIndicator>
Show History
</DropdownMenu.CheckboxItem>
<DropdownMenu.Separator class="dropdown-menu__separator" />
<DropdownMenu.Group>
<DropdownMenu.GroupLabel class="dropdown-menu__group-label">
Branches
</DropdownMenu.GroupLabel>
<DropdownMenu.RadioGroup value={branch()} onChange={setBranch}>
<DropdownMenu.RadioItem class="dropdown-menu__radio-item" value="main">
<DropdownMenu.ItemIndicator class="dropdown-menu__item-indicator">
<DotFilledIcon />
</DropdownMenu.ItemIndicator>
main
</DropdownMenu.RadioItem>
<DropdownMenu.RadioItem class="dropdown-menu__radio-item" value="develop">
<DropdownMenu.ItemIndicator class="dropdown-menu__item-indicator">
<DotFilledIcon />
</DropdownMenu.ItemIndicator>
develop
</DropdownMenu.RadioItem>
</DropdownMenu.RadioGroup>
</DropdownMenu.Group>
<DropdownMenu.Arrow />
</DropdownMenu.Content>
</DropdownMenu.Portal>
</DropdownMenu>
);
}
```
```
Copytsx
import { DropdownMenu } from "@kobalte/core/dropdown-menu";
import { createSignal } from "solid-js";
import { CheckIcon, ChevronDownIcon, ChevronRightIcon, DotFilledIcon } from "some-icon-library";
import "./style.css";
function App() {
const [showGitLog, setShowGitLog] = createSignal(true);
const [showHistory, setShowHistory] = createSignal(false);
const [branch, setBranch] = createSignal("main");
return (
<DropdownMenu>
<DropdownMenu.Trigger class="dropdown-menu__trigger">
<span>Git Settings</span>
<DropdownMenu.Icon class="dropdown-menu__trigger-icon">
<ChevronDownIcon />
</DropdownMenu.Icon>
</DropdownMenu.Trigger>
<DropdownMenu.Portal>
<DropdownMenu.Content class="dropdown-menu__content">
<DropdownMenu.Item class="dropdown-menu__item">
Commit <div class="dropdown-menu__item-right-slot">⌘+K</div>
</DropdownMenu.Item>
<DropdownMenu.Item class="dropdown-menu__item">
Push <div class="dropdown-menu__item-right-slot">⇧+⌘+K</div>
</DropdownMenu.Item>
<DropdownMenu.Item class="dropdown-menu__item" disabled>
Update Project <div class="dropdown-menu__item-right-slot">⌘+T</div>
</DropdownMenu.Item>
<DropdownMenu.Sub overlap gutter={4} shift={-8}>
<DropdownMenu.SubTrigger class="dropdown-menu__sub-trigger">
GitHub
<div class="dropdown-menu__item-right-slot">
<ChevronRightIcon width={20} height={20} />
</div>
</DropdownMenu.SubTrigger>
<DropdownMenu.Portal>
<DropdownMenu.SubContent class="dropdown-menu__sub-content">
<DropdownMenu.Item class="dropdown-menu__item">
Create Pull Request…
</DropdownMenu.Item>
<DropdownMenu.Item class="dropdown-menu__item">
View Pull Requests
</DropdownMenu.Item>
<DropdownMenu.Item class="dropdown-menu__item">
Sync Fork
</DropdownMenu.Item>
<DropdownMenu.Separator class="dropdown-menu__separator" />
<DropdownMenu.Item class="dropdown-menu__item">
Open on GitHub
</DropdownMenu.Item>
</DropdownMenu.SubContent>
</DropdownMenu.Portal>
</DropdownMenu.Sub>
<DropdownMenu.Separator class="dropdown-menu__separator" />
<DropdownMenu.CheckboxItem
class="dropdown-menu__checkbox-item"
checked={showGitLog()}
onChange={setShowGitLog}
>
<DropdownMenu.ItemIndicator class="dropdown-menu__item-indicator">
<CheckIcon />
</DropdownMenu.ItemIndicator>
Show Git Log
</DropdownMenu.CheckboxItem>
<DropdownMenu.CheckboxItem
class="dropdown-menu__checkbox-item"
checked={showHistory()}
onChange={setShowHistory}
>
<DropdownMenu.ItemIndicator class="dropdown-menu__item-indicator">
<CheckIcon />
</DropdownMenu.ItemIndicator>
Show History
</DropdownMenu.CheckboxItem>
<DropdownMenu.Separator class="dropdown-menu__separator" />
<DropdownMenu.Group>
<DropdownMenu.GroupLabel class="dropdown-menu__group-label">
Branches
</DropdownMenu.GroupLabel>
<DropdownMenu.RadioGroup value={branch()} onChange={setBranch}>
<DropdownMenu.RadioItem class="dropdown-menu__radio-item" value="main">
<DropdownMenu.ItemIndicator class="dropdown-menu__item-indicator">
<DotFilledIcon />
</DropdownMenu.ItemIndicator>
main
</DropdownMenu.RadioItem>
<DropdownMenu.RadioItem class="dropdown-menu__radio-item" value="develop">
<DropdownMenu.ItemIndicator class="dropdown-menu__item-indicator">
<DotFilledIcon />
</DropdownMenu.ItemIndicator>
develop
</DropdownMenu.RadioItem>
</DropdownMenu.RadioGroup>
</DropdownMenu.Group>
<DropdownMenu.Arrow />
</DropdownMenu.Content>
</DropdownMenu.Portal>
</DropdownMenu>
);
}
```
## Usage
### Default open
An initial, uncontrolled open value can be provided using the `defaultOpen` prop.
```
Copytsx
<DropdownMenu defaultOpen>...</DropdownMenu>
```
```
Copytsx
<DropdownMenu defaultOpen>...</DropdownMenu>
```
### Controlled open
The `open` prop can be used to make the open state controlled. The `onOpenChange` event is fired when the user presses the trigger, an item or interact outside, and receives the new value.
Open
```
Copytsx
import { createSignal } from "solid-js";
function ControlledExample() {
const [open, setOpen] = createSignal(false);
return (
<DropdownMenu open={open()} onOpenChange={setOpen}>
<DropdownMenu.Trigger>{open() ? "Close" : "Open"}</DropdownMenu.Trigger>
<DropdownMenu.Portal>
<DropdownMenu.Content>...</DropdownMenu.Content>
</DropdownMenu.Portal>
</DropdownMenu>
);
}
```
```
Copytsx
import { createSignal } from "solid-js";
function ControlledExample() {
const [open, setOpen] = createSignal(false);
return (
<DropdownMenu open={open()} onOpenChange={setOpen}>
<DropdownMenu.Trigger>{open() ? "Close" : "Open"}</DropdownMenu.Trigger>
<DropdownMenu.Portal>
<DropdownMenu.Content>...</DropdownMenu.Content>
</DropdownMenu.Portal>
</DropdownMenu>
);
}
```
### Origin-aware animations
We expose a CSS custom property `--kb-menu-content-transform-origin` which can be used to animate the content from its computed origin.
```
Copycss
/* style.css */
.dropdown-menu__content,
.dropdown-menu__sub-content {
transform-origin: var(--kb-menu-content-transform-origin);
animation: contentHide 250ms ease-in forwards;
}
.dropdown-menu__content[data-expanded],
.dropdown-menu__sub-content[data-expanded] {
animation: contentShow 250ms ease-out;
}
@keyframes contentShow {
from {
opacity: 0;
transform: scale(0.96);
}
to {
opacity: 1;
transform: scale(1);
}
}
@keyframes contentHide {
from {
opacity: 1;
transform: scale(1);
}
to {
opacity: 0;
transform: scale(0.96);
}
}
```
```
Copycss
/* style.css */
.dropdown-menu__content,
.dropdown-menu__sub-content {
transform-origin: var(--kb-menu-content-transform-origin);
animation: contentHide 250ms ease-in forwards;
}
.dropdown-menu__content[data-expanded],
.dropdown-menu__sub-content[data-expanded] {
animation: contentShow 250ms ease-out;
}
@keyframes contentShow {
from {
opacity: 0;
transform: scale(0.96);
}
to {
opacity: 1;
transform: scale(1);
}
}
@keyframes contentHide {
from {
opacity: 1;
transform: scale(1);
}
to {
opacity: 0;
transform: scale(0.96);
}
}
```
## API Reference
### DropdownMenu
`DropdownMenu` is equivalent to the `Root` import from `@kobalte/core/dropdown-menu` (and deprecated `DropdownMenu.Root`).
| Prop | Description |
| --- | --- |
| open | `boolean`<br> The controlled open state of the menu. |
| defaultOpen | `boolean`<br> The default open state when initially rendered. Useful when you do not need to control the open state. |
| onOpenChange | `(open: boolean) => void`<br> Event handler called when the open state of the menu changes. |
| id | `string`<br> A unique identifier for the component. The id is used to generate id attributes for nested components. If no id prop is provided, a generated id will be used. |
| modal | `boolean`<br> Whether the menu should be the only visible content for screen readers, when set to `true`: <br> \- interaction with outside elements will be disabled. <br> \- scroll will be locked. <br> \- focus will be locked inside the menu content. <br> \- elements outside the menu content will not be visible for screen readers. |
| preventScroll | `boolean`<br> Whether the scroll should be locked even if the menu is not modal. |
| forceMount | `boolean`<br> Used to force mounting the menu (portal and content) when more control is needed. Useful when controlling animation with SolidJS animation libraries. |
`DropdownMenu` also accepts the following props to customize the placement of the `DropdownMenu.Content`.
| Prop | Description |
| --- | --- |
| getAnchorRect | `(anchor?: HTMLElement) => AnchorRect | undefined`<br> Function that returns the trigger element's DOMRect. |
| placement | `Placement`<br> The placement of the menu content. |
| gutter | `number`<br> The distance between the menu content and the trigger element. By default, it's 0 plus half of the arrow offset, if it exists. |
| shift | `number`<br> The skidding of the menu content along the trigger element. |
| flip | `boolean | string`<br> Controls the behavior of the menu content when it overflows the viewport:<br> \- If a `boolean`, specifies whether the menu content should flip to the opposite side when it overflows.<br> \- If a `string`, indicates the preferred fallback placements when it overflows.<br>The placements must be spaced-delimited, e.g. "top left". |
| slide | `boolean`<br> Whether the menu content should slide when it overflows. |
| overlap | `boolean`<br> Whether the menu content can overlap the trigger element when it overflows. |
| sameWidth | `boolean`<br> Whether the menu content should have the same width as the trigger element. This will be exposed to CSS as `--kb-popper-anchor-width`. |
| fitViewport | `boolean`<br> Whether the menu content should fit the viewport. If this is set to true, the menu content will have `maxWidth` and `maxHeight` set to the viewport size. This will be exposed to CSS as `--kb-popper-content-available-width` and `--kb-popper-content-available-height`. |
| hideWhenDetached | `boolean`<br> Whether to hide the menu content when the trigger element becomes occluded. |
| detachedPadding | `number`<br> The minimum padding in order to consider the trigger element occluded. |
| arrowPadding | `number`<br> The minimum padding between the arrow and the menu content corner. |
| overflowPadding | `number`<br> The minimum padding between the menu content and the viewport edge. This will be exposed to CSS as `--kb-popper-overflow-padding`. |
### DropdownMenu.Trigger
`DropdownMenu.Trigger` consists of [Button](https://kobalte.dev/docs/core/components/button).
| Data attribute | Description |
| --- | --- |
| data-expanded | Present when the menu is open. |
| data-closed | Present when the menu is close. |
`DropdownMenu.Icon`, `DropdownMenu.Content`, `DropdownMenu.SubTrigger` and `DropdownMenu.SubContent` share the same data-attributes.
### DropdownMenu.Content
The popper positioner will copy the same `z-index` as the `DropdownMenu.Content`.
| Prop | Description |
| --- | --- |
| onOpenAutoFocus | `(event: Event) => void`<br> Event handler called when focus moves into the component after opening. It can be prevented by calling `event.preventDefault`. |
| onCloseAutoFocus | `(event: Event) => void`<br> Event handler called when focus moves to the trigger after closing. It can be prevented by calling `event.preventDefault`. |
| onEscapeKeyDown | `(event: KeyboardEvent) => void`<br> Event handler called when the escape key is down. It can be prevented by calling `event.preventDefault`. |
| onPointerDownOutside | `(event: PointerDownOutsideEvent) => void`<br> Event handler called when a pointer event occurs outside the bounds of the component. It can be prevented by calling `event.preventDefault`. |
| onFocusOutside | `(event: FocusOutsideEvent) => void`<br> Event handler called when the focus moves outside the bounds of the component. It can be prevented by calling `event.preventDefault`. |
| onInteractOutside | `(event: InteractOutsideEvent) => void`<br> Event handler called when an interaction (pointer or focus event) happens outside the bounds of the component. It can be prevented by calling `event.preventDefault`. |
### DropdownMenu.Arrow
| Prop | Description |
| --- | --- |
| size | `number`<br> The size of the arrow. |
### DropdownMenu.Item
| Prop | Description |
| --- | --- |
| textValue | `string`<br> Optional text used for typeahead purposes. By default, the typeahead behavior will use the .textContent of the `DropdownMenu.ItemLabel` part if provided, or fallback to the .textContent of the `DropdownMenu.Item`. Use this when the content is complex, or you have non-textual content inside. |
| disabled | `boolean`<br> Whether the item is disabled or not. |
| closeOnSelect | `boolean`<br> Whether the menu should close when the item is activated. |
| onSelect | `() => void`<br> Event handler called when the user selects an item (via mouse or keyboard). |
| Data attribute | Description |
| --- | --- |
| data-disabled | Present when the item is disabled. |
| data-highlighted | Present when the item is highlighted. |
`DropdownMenu.ItemLabel`, `DropdownMenu.ItemDescription` and `DropdownMenu.ItemIndicator` shares the same data-attributes.
### DropdownMenu.ItemIndicator
| Prop | Description |
| --- | --- |
| forceMount | `boolean`<br> Used to force mounting when more control is needed. Useful when controlling animation with SolidJS animation libraries. |
### DropdownMenu.RadioGroup
| Prop | Description |
| --- | --- |
| value | `string`<br> The controlled value of the menu radio item to check. |
| defaultValue | `string`<br> The value of the menu radio item that should be checked when initially rendered. Useful when you do not need to control the state of the radio group. |
| onChange | `(value: string) => void`<br> Event handler called when the value changes. |
| disabled | `boolean`<br> Whether the radio group is disabled or not. |
### DropdownMenu.RadioItem
| Prop | Description |
| --- | --- |
| value | `string`<br> The value of the menu item radio. |
| textValue | `string`<br> Optional text used for typeahead purposes. By default, the typeahead behavior will use the .textContent of the `DropdownMenu.ItemLabel` part if provided, or fallback to the .textContent of the `DropdownMenu.Item`. Use this when the content is complex, or you have non-textual content inside. |
| disabled | `boolean`<br> Whether the item is disabled or not. |
| closeOnSelect | `boolean`<br> Whether the menu should close when the item is checked. |
| onSelect | `() => void`<br> Event handler called when the user selects an item (via mouse or keyboard). |
| Data attribute | Description |
| --- | --- |
| data-disabled | Present when the item is disabled. |
| data-checked | Present when the item is checked. |
| data-highlighted | Present when the item is highlighted. |
### DropdownMenu.CheckboxItem
| Prop | Description |
| --- | --- |
| checked | `boolean`<br> The controlled checked state of the item. |
| defaultChecked | `boolean`<br> The default checked state when initially rendered. Useful when you do not need to control the checked state. |
| onChange | `(checked: boolean) => void`<br> Event handler called when the checked state of the item changes. |
| textValue | `string`<br> Optional text used for typeahead purposes. By default, the typeahead behavior will use the .textContent of the `DropdownMenu.ItemLabel` part if provided, or fallback to the .textContent of the `DropdownMenu.Item`. Use this when the content is complex, or you have non-textual content inside. |
| indeterminate | `boolean`<br> Whether the item is in an indeterminate state. |
| disabled | `boolean`<br> Whether the item is disabled or not. |
| closeOnSelect | `boolean`<br> Whether the menu should close when the item is checked/unchecked. |
| onSelect | `() => void`<br> Event handler called when the user selects an item (via mouse or keyboard). |
| Data attribute | Description |
| --- | --- |
| data-disabled | Present when the item is disabled. |
| data-indeterminate | Present when the item is in an indeterminate state. |
| data-checked | Present when the item is checked. |
| data-highlighted | Present when the item is highlighted. |
### DropdownMenu.Sub
| Prop | Description |
| --- | --- |
| open | `boolean`<br> The controlled open state of the sub menu. |
| defaultOpen | `boolean`<br> The default open state when initially rendered. Useful when you do not need to control the open state. |
| onOpenChange | `(open: boolean) => void`<br> Event handler called when the open state of the sub menu changes. |
`DropdownMenu.Sub` also accepts the following props to customize the placement of the `DropdownMenu.SubContent`.
| Prop | Description |
| --- | --- |
| getAnchorRect | `(anchor?: HTMLElement) => AnchorRect | undefined`<br> Function that returns the trigger element's DOMRect. |
| gutter | `number`<br> The distance between the sub menu content and the trigger element. By default, it's 0 plus half of the arrow offset, if it exists. |
| shift | `number`<br> The skidding of the sub menu content along the trigger element. |
| slide | `boolean`<br> Whether the sub menu content should slide when it overflows. |
| overlap | `boolean`<br> Whether the sub menu content can overlap the trigger element when it overflows. |
| fitViewport | `boolean`<br> Whether the sub menu content should fit the viewport. If this is set to true, the sub menu content will have `maxWidth` and `maxHeight` set to the viewport size. This will be exposed to CSS as `--kb-popper-content-available-width` and `--kb-popper-content-available-height`. |
| hideWhenDetached | `boolean`<br> Whether to hide the sub menu content when the trigger element becomes occluded. |
| detachedPadding | `number`<br> The minimum padding in order to consider the trigger element occluded. |
| arrowPadding | `number`<br> The minimum padding between the arrow and the sub menu content corner. |
| overflowPadding | `number`<br> The minimum padding between the sub menu content and the viewport edge. This will be exposed to CSS as `--kb-popper-overflow-padding`. |
### DropdownMenu.SubTrigger
| Prop | Description |
| --- | --- |
| textValue | `string`<br> Optional text used for typeahead purposes. By default, the typeahead behavior will use the .textContent of the `DropdownMenu.SubTrigger`. Use this when the content is complex, or you have non-textual content inside. |
| disabled | `boolean`<br> Whether the sub menu trigger is disabled or not. |
| Data attribute | Description |
| --- | --- |
| data-disabled | Present when the item is disabled. |
| data-highlighted | Present when the item is highlighted. |
### DropdownMenu.SubContent
| Prop | Description |
| --- | --- |
| onEscapeKeyDown | `(event: KeyboardEvent) => void`<br> Event handler called when the escape key is down. It can be prevented by calling `event.preventDefault`. |
| onPointerDownOutside | `(event: PointerDownOutsideEvent) => void`<br> Event handler called when a pointer event occurs outside the bounds of the component. It can be prevented by calling `event.preventDefault`. |
| onFocusOutside | `(event: FocusOutsideEvent) => void`<br> Event handler called when the focus moves outside the bounds of the component. It can be prevented by calling `event.preventDefault`. |
| onInteractOutside | `(event: InteractOutsideEvent) => void`<br> Event handler called when an interaction (pointer or focus event) happens outside the bounds of the component. It can be prevented by calling `event.preventDefault`. |
## Rendered elements
| Component | Default rendered element |
| --- | --- |
| `DropdownMenu` | none |
| `DropdownMenu.Trigger` | `button` |
| `DropdownMenu.Icon` | `span` |
| `DropdownMenu.Portal` | `Portal` |
| `DropdownMenu.Content` | `div` |
| `DropdownMenu.Arrow` | `div` |
| `DropdownMenu.Separator` | `hr` |
| `DropdownMenu.Group` | `div` |
| `DropdownMenu.GroupLabel` | `span` |
| `DropdownMenu.Sub` | none |
| `DropdownMenu.SubTrigger` | `div` |
| `DropdownMenu.SubContent` | `div` |
| `DropdownMenu.Item` | `div` |
| `DropdownMenu.ItemLabel` | `div` |
| `DropdownMenu.ItemDescription` | `div` |
| `DropdownMenu.ItemIndicator` | `div` |
| `DropdownMenu.RadioGroup` | `div` |
| `DropdownMenu.RadioItem` | `div` |
| `DropdownMenu.CheckboxItem` | `div` |
## Accessibility
### Keyboard Interactions
| Key | Description |
| --- | --- |
| `Space` | When focus is on the trigger, opens the menu and focuses the first item. <br> When focus is on an item, activates the item. |
| `Enter` | When focus is on the trigger, opens the menu and focuses the first item. <br> When focus is on an item, activates the item. |
| `ArrowDown` | When focus is on the trigger, opens the menu and focuses the first item. <br> When focus is on an item, moves focus to the next item. |
| `ArrowUp` | When focus is on the trigger, opens the menu and focuses the last item. <br> When focus is on an item, moves focus to the previous item. |
| `ArrowRight` / `ArrowLeft` | When focus is on a sub menu trigger, opens or closes the submenu depending on reading direction. |
| `Home` | When focus is on an item, moves focus to first item. |
| `End` | When focus is on an item, moves focus to last item. |
| `Esc` | Closes the menu and moves focus to the trigger. |
Previous[←Dialog](https://kobalte.dev/docs/core/components/dialog)Next[File Field→](https://kobalte.dev/docs/core/components/file-field)

View File

@ -0,0 +1,331 @@
# Hover Card
Allows sighted users to preview content available behind a link.
## Import
```
Copyts
import { HoverCard } from "@kobalte/core/hover-card";
// or
import { Root, Trigger, ... } from "@kobalte/core/hover-card";
// or (deprecated)
import { HoverCard } from "@kobalte/core";
```
```
Copyts
import { HoverCard } from "@kobalte/core/hover-card";
// or
import { Root, Trigger, ... } from "@kobalte/core/hover-card";
// or (deprecated)
import { HoverCard } from "@kobalte/core";
```
## Features
- Opens on hover only.
- Supports custom open and close delays.
- Optionally render a pointing arrow.
- Ignored by screen readers.
- Can be controlled or uncontrolled.
## Anatomy
The hovercard consists of:
- **HoverCard:** The root container for a hovercard.
- **HoverCard.Trigger:** The link that opens the hovercard.
- **HoverCard.Portal:** Portals its children into the `body` when the hovercard is open.
- **HoverCard.Content:** Contains the content to be rendered when the hovercard is open.
- **HoverCard.Arrow:** An optional arrow element to render alongside the hovercard.
```
Copytsx
<HoverCard>
<HoverCard.Trigger />
<HoverCard.Portal>
<HoverCard.Content>
<HoverCard.Arrow />
</HoverCard.Content>
</HoverCard.Portal>
</HoverCard>
```
```
Copytsx
<HoverCard>
<HoverCard.Trigger />
<HoverCard.Portal>
<HoverCard.Content>
<HoverCard.Arrow />
</HoverCard.Content>
</HoverCard.Portal>
</HoverCard>
```
## Example
[@MLFabien](https://twitter.com/mlfabien)
index.tsxstyle.css
```
Copytsx
import { HoverCard } from "@kobalte/core/hover-card";
import "./style.css";
function App() {
return (
<HoverCard>
<HoverCard.Trigger
class="hovercard__trigger"
href="https://twitter.com/mlfabien"
target="_blank"
>
@MLFabien
</HoverCard.Trigger>
<HoverCard.Portal>
<HoverCard.Content class="hovercard__content">
<HoverCard.Arrow />
<img
src="https://pbs.twimg.com/profile_images/1509139491671445507/pzWYjlYN_400x400.jpg"
alt="Fabien MARIE-LOUISE"
class="hovercard__avatar"
/>
<h2 class="hovercard__title">Fabien MARIE-LOUISE</h2>
<p class="hovercard__description">
Developer and UI Design enthusiast. Building UI related stuffs for @solid_js
</p>
</HoverCard.Content>
</HoverCard.Portal>
</HoverCard>
);
}
```
```
Copytsx
import { HoverCard } from "@kobalte/core/hover-card";
import "./style.css";
function App() {
return (
<HoverCard>
<HoverCard.Trigger
class="hovercard__trigger"
href="https://twitter.com/mlfabien"
target="_blank"
>
@MLFabien
</HoverCard.Trigger>
<HoverCard.Portal>
<HoverCard.Content class="hovercard__content">
<HoverCard.Arrow />
<img
src="https://pbs.twimg.com/profile_images/1509139491671445507/pzWYjlYN_400x400.jpg"
alt="Fabien MARIE-LOUISE"
class="hovercard__avatar"
/>
<h2 class="hovercard__title">Fabien MARIE-LOUISE</h2>
<p class="hovercard__description">
Developer and UI Design enthusiast. Building UI related stuffs for @solid_js
</p>
</HoverCard.Content>
</HoverCard.Portal>
</HoverCard>
);
}
```
## Usage
### Default open
An initial, uncontrolled open value can be provided using the `defaultOpen` prop.
```
Copytsx
<HoverCard defaultOpen>...</HoverCard>
```
```
Copytsx
<HoverCard defaultOpen>...</HoverCard>
```
### Controlled open
The `open` prop can be used to make the open state controlled. The `onOpenChange` event is fired when the user pointer enter or leave the trigger, and receives the new value.
```
Copytsx
import { createSignal } from "solid-js";
function ControlledExample() {
const [open, setOpen] = createSignal(false);
return (
<HoverCard open={open()} onOpenChange={setOpen}>
...
</HoverCard>
);
}
```
```
Copytsx
import { createSignal } from "solid-js";
function ControlledExample() {
const [open, setOpen] = createSignal(false);
return (
<HoverCard open={open()} onOpenChange={setOpen}>
...
</HoverCard>
);
}
```
### Origin-aware animations
We expose a CSS custom property `--kb-hovercard-content-transform-origin` which can be used to animate the content from its computed origin.
```
Copycss
/* style.css */
.hovercard__content {
transform-origin: var(--kb-hovercard-content-transform-origin);
animation: contentHide 250ms ease-in forwards;
}
.hovercard__content[data-expanded] {
animation: contentShow 250ms ease-out;
}
@keyframes contentShow {
from {
opacity: 0;
transform: scale(0.96);
}
to {
opacity: 1;
transform: scale(1);
}
}
@keyframes contentHide {
from {
opacity: 1;
transform: scale(1);
}
to {
opacity: 0;
transform: scale(0.96);
}
}
```
```
Copycss
/* style.css */
.hovercard__content {
transform-origin: var(--kb-hovercard-content-transform-origin);
animation: contentHide 250ms ease-in forwards;
}
.hovercard__content[data-expanded] {
animation: contentShow 250ms ease-out;
}
@keyframes contentShow {
from {
opacity: 0;
transform: scale(0.96);
}
to {
opacity: 1;
transform: scale(1);
}
}
@keyframes contentHide {
from {
opacity: 1;
transform: scale(1);
}
to {
opacity: 0;
transform: scale(0.96);
}
}
```
## API Reference
### HoverCard
`HoverCard` is equivalent to the `Root` import from `@kobalte/core/hover-card` (and deprecated `HoverCard.Root`).
| Prop | Description |
| --- | --- |
| open | `boolean`<br> The controlled open state of the hovercard. |
| defaultOpen | `boolean`<br> The default open state when initially rendered. Useful when you do not need to control the open state. |
| onOpenChange | `(open: boolean) => void`<br> Event handler called when the open state of the hovercard changes. |
| openDelay | `number`<br> The duration from when the mouse enters the trigger until the hovercard opens. |
| closeDelay | `number`<br> The duration from when the mouse leaves the trigger or content until the hovercard closes. |
| ignoreSafeArea | `boolean`<br> Whether to close the hovercard even if the user cursor is inside the safe area between the trigger and hovercard. |
| id | `string`<br> A unique identifier for the component. The id is used to generate id attributes for nested components. If no id prop is provided, a generated id will be used. |
| forceMount | `boolean`<br> Used to force mounting the hovercard (portal and content) when more control is needed. Useful when controlling animation with SolidJS animation libraries. |
`HoverCard` also accepts the following props to customize the placement of the `HoverCard.Content`.
| Prop | Description |
| --- | --- |
| getAnchorRect | `(anchor?: HTMLElement) => AnchorRect | undefined`<br> Function that returns the anchor element's DOMRect. |
| placement | `Placement`<br> The placement of the hovercard. |
| gutter | `number`<br> The distance between the hovercard and the trigger element. By default, it's 0 plus half of the arrow offset, if it exists. |
| shift | `number`<br> The skidding of the hovercard along the trigger element. |
| flip | `boolean | string`<br> Controls the behavior of the hovercard when it overflows the viewport:<br> \- If a `boolean`, specifies whether the hovercard should flip to the opposite side when it overflows.<br> \- If a `string`, indicates the preferred fallback placements when it overflows.<br>The placements must be spaced-delimited, e.g. "top left". |
| slide | `boolean`<br> Whether the hovercard should slide when it overflows. |
| overlap | `boolean`<br> Whether the hovercard can overlap the trigger element when it overflows. |
| sameWidth | `boolean`<br> Whether the hovercard should have the same width as the trigger element. This will be exposed to CSS as `--kb-popper-anchor-width`. |
| fitViewport | `boolean`<br> Whether the hovercard should fit the viewport. If this is set to true, the hovercard content will have `maxWidth` and `maxHeight` set to the viewport size. This will be exposed to CSS as `--kb-popper-available-width` and `--kb-popper-available-height`. |
| hideWhenDetached | `boolean`<br> Whether to hide the hovercard when the trigger element becomes occluded. |
| detachedPadding | `number`<br> The minimum padding in order to consider the trigger element occluded. |
| arrowPadding | `number`<br> The minimum padding between the arrow and the hovercard corner. |
| overflowPadding | `number`<br> The minimum padding between the hovercard and the viewport edge. This will be exposed to CSS as `--kb-popper-overflow-padding`. |
### HoverCard.Content
The popper positioner will copy the same `z-index` as the `HoverCard.Content`.
### HoverCard.Trigger
`HoverCard.Trigger` consists of [Link](https://kobalte.dev/docs/core/components/link).
| Data attribute | Description |
| --- | --- |
| data-expanded | Present when the hovercard is open. |
| data-closed | Present when the hovercard is close. |
`HoverCard.Content` share the same data-attributes.
## Rendered elements
| Component | Default rendered element |
| --- | --- |
| `HoverCard` | none |
| `HoverCard.Trigger` | `a` |
| `HoverCard.Portal` | `Portal` |
| `HoverCard.Content` | `div` |
| `HoverCard.Arrow` | `div` |
## Accessibility
### Keyboard Interactions
The hover card is intended for mouse users only so will not respond to keyboard navigation.
Previous[←File Field](https://kobalte.dev/docs/core/components/file-field)Next[Image→](https://kobalte.dev/docs/core/components/image)

View File

@ -0,0 +1,97 @@
# I18nProvider
Provides the locale for the application to all child components.
## Import
```
Copytsx
import { I18nProvider, useLocale } from "@kobalte/core/i18n";
// or (deprecated)
import { I18nProvider, useLocale } from "@kobalte/core";
```
```
Copytsx
import { I18nProvider, useLocale } from "@kobalte/core/i18n";
// or (deprecated)
import { I18nProvider, useLocale } from "@kobalte/core";
```
## Usage
`I18nProvider` allows you to override the default locale as determined by the browser/system setting with a locale defined by your application (e.g. application setting).
This should be done by wrapping your entire application in the provider, which will cause all child elements to receive the new locale information via the `useLocale` primitive.
```
Copytsx
import { I18nProvider } from "@kobalte/core/i18n";
<I18nProvider locale="fr-FR">
<YourApp />
</I18nProvider>;
```
```
Copytsx
import { I18nProvider } from "@kobalte/core/i18n";
<I18nProvider locale="fr-FR">
<YourApp />
</I18nProvider>;
```
### The `useLocale` primitive
`useLocale` allows components to access the current locale and interface layout direction. It should be used in the root of your app to define the lang and dir attributes so that the browser knows which language and direction the user interface should be rendered in.
```
Copytsx
import { useLocale } from "@kobalte/core/i18n";
function YourApp() {
const { locale, direction } = useLocale();
return (
<div lang={locale()} dir={direction()}>
{/* your app here */}
</div>
);
}
```
```
Copytsx
import { useLocale } from "@kobalte/core/i18n";
function YourApp() {
const { locale, direction } = useLocale();
return (
<div lang={locale()} dir={direction()}>
{/* your app here */}
</div>
);
}
```
## API reference
### I18nProvider
| Prop | Description |
| --- | --- |
| locale | `string`<br> The locale to apply to the children. |
| children | `JSX.Element`<br> The contents that should have the locale applied. |
### useLocale
The `useLocale` primitive returns the follows properties.
| Name | Description |
| --- | --- |
| locale | `Accessor<string>`<br> The [BCP47](https://www.ietf.org/rfc/bcp/bcp47.txt) language code for the locale. |
| direction | `Accessor<Direction>`<br> The writing direction for the locale. |
Previous[←Tooltip](https://kobalte.dev/docs/core/components/tooltip)Next→

View File

@ -0,0 +1,160 @@
# Image
An image element with an optional fallback for loading and error status.
## Import
```
Copyts
import { Image } from "@kobalte/core/image";
// or
import { Root, Img, ... } from "@kobalte/core/image";
// or (deprecated)
import { Image } from "@kobalte/core";
```
```
Copyts
import { Image } from "@kobalte/core/image";
// or
import { Root, Img, ... } from "@kobalte/core/image";
// or (deprecated)
import { Image } from "@kobalte/core";
```
## Features
- Automatic and manual control over when the image renders.
- Fallback part accepts any children.
- Optionally delay fallback rendering to avoid content flashing.
## Anatomy
The image consists of:
- **Image:** The root container for an image.
- **Image.Img:** The image to render. By default, it will only render when it has loaded.
- **Image.Fallback:** An element that renders when the image hasn't loaded. This means whilst it's loading, or if there was an error.
```
Copytsx
<Image>
<Image.Img />
<Image.Fallback />
</Image>
```
```
Copytsx
<Image>
<Image.Img />
<Image.Fallback />
</Image>
```
## Example
![Nicole Steeves](https://randomuser.me/api/portraits/women/44.jpg)MD
index.tsxstyle.css
```
Copytsx
import { Image } from "@kobalte/core/image";
import "./style.css";
function App() {
return (
<>
<Image fallbackDelay={600} class="image">
<Image.Img
class="image__img"
src="https://randomuser.me/api/portraits/women/44.jpg"
alt="Nicole Steeves"
/>
<Image.Fallback class="image__fallback">NS</Image.Fallback>
</Image>
<Image class="image">
<Image.Fallback class="image__fallback">MD</Image.Fallback>
</Image>
</>
);
}
```
```
Copytsx
import { Image } from "@kobalte/core/image";
import "./style.css";
function App() {
return (
<>
<Image fallbackDelay={600} class="image">
<Image.Img
class="image__img"
src="https://randomuser.me/api/portraits/women/44.jpg"
alt="Nicole Steeves"
/>
<Image.Fallback class="image__fallback">NS</Image.Fallback>
</Image>
<Image class="image">
<Image.Fallback class="image__fallback">MD</Image.Fallback>
</Image>
</>
);
}
```
## Usage
### Avoid flash during loading
By default `Image.Fallback` will render when the image hasn't loaded. This means whilst it's loading, or if there was an error.
If you notice a flash during loading, use the `fallbackDelay` prop to delay its rendering, so it only renders for those with slower internet connections.
```
Copytsx
<Image fallbackDelay={600} class="image">
<Image.Img
class="image__img"
src="https://randomuser.me/api/portraits/women/44.jpg"
alt="Nicole Steeves"
/>
<Image.Fallback class="image__fallback">NS</Image.Fallback>
</Image>
```
```
Copytsx
<Image fallbackDelay={600} class="image">
<Image.Img
class="image__img"
src="https://randomuser.me/api/portraits/women/44.jpg"
alt="Nicole Steeves"
/>
<Image.Fallback class="image__fallback">NS</Image.Fallback>
</Image>
```
## API Reference
### Image
`Image` is equivalent to the `Root` import from `@kobalte/core/image` (and deprecated `Image.Root`).
| Prop | Description |
| --- | --- |
| fallbackDelay | `number`<br> The delay (in ms) before displaying the image fallback. Useful if you notice a flash during loading for delaying rendering, so it only appears for those with slower internet connections. |
| onLoadingStatusChange | `(status: "idle" | "loading" | "loaded" | "error") => void`<br> A callback providing information about the loading status of the image. This is useful in case you want to control more precisely what to render as the image is loading. |
## Rendered elements
| Component | Default rendered element |
| --- | --- |
| `Image` | `span` |
| `Image.Img` | `img` |
| `Image.Fallback` | `span` |
Previous[←Hover Card](https://kobalte.dev/docs/core/components/hover-card)Next[Link→](https://kobalte.dev/docs/core/components/link)

View File

@ -0,0 +1,131 @@
# Link
Allows a user to navigate to another page or resource within a web page or application.
## Import
```
Copyts
import { Link } from "@kobalte/core/link";
// or
import { Root } from "@kobalte/core/link";
// or (deprecated)
import { Link } from "@kobalte/core";
```
```
Copyts
import { Link } from "@kobalte/core/link";
// or
import { Root } from "@kobalte/core/link";
// or (deprecated)
import { Link } from "@kobalte/core";
```
## Features
- Native HTML `<a>` element support.
- Custom element type support via the [WAI ARIA Link](https://www.w3.org/WAI/ARIA/apg/patterns/link/) design pattern.
- Support for disabled links.
## Anatomy
The link consists of:
- **Link:** The root container for a link.
```
Copytsx
<Link />
```
```
Copytsx
<Link />
```
## Example
[Kobalte](https://kobalte.dev/)
index.tsxstyle.css
```
Copytsx
import { Link } from "@kobalte/core/link";
import "./style.css";
function App() {
return (
<Link class="link" href="https://kobalte.dev">
Kobalte
</Link>
);
}
```
```
Copytsx
import { Link } from "@kobalte/core/link";
import "./style.css";
function App() {
return (
<Link class="link" href="https://kobalte.dev">
Kobalte
</Link>
);
}
```
## Usage
### Disabled state
Use the `disabled` prop to disable a link while keeping it accessible for screen readers.
Kobalte
```
Copytsx
<Link href="https://kobalte.dev" disabled>
Kobalte
</Link>
```
```
Copytsx
<Link href="https://kobalte.dev" disabled>
Kobalte
</Link>
```
## API Reference
### Link
`Link` is equivalent to the `Root` import from `@kobalte/core/link` (and deprecated `Link.Root`).
| Prop | Description |
| --- | --- |
| disabled | `boolean`<br> Whether the link is disabled. Native navigation will be disabled, and the link will be exposed as disabled to assistive technology. |
| Data attribute | Description |
| --- | --- |
| data-disabled | Present when the link is disabled. |
## Rendered elements
| Component | Default rendered element |
| --- | --- |
| `Link` | `a` |
## Accessibility
### Keyboard Interactions
| Key | Description |
| --- | --- |
| `Enter` | Activates the link. |
Previous[←Image](https://kobalte.dev/docs/core/components/image)Next[Menubar→](https://kobalte.dev/docs/core/components/menubar)

View File

@ -0,0 +1,221 @@
# Meter
Displays numeric value that varies within a defined range
## Import
```
Copyts
import { Meter } from "@kobalte/core/meter";
// or
import { Root, Label, ... } from "@kobalte/core/meter";
```
```
Copyts
import { Meter } from "@kobalte/core/meter";
// or
import { Root, Label, ... } from "@kobalte/core/meter";
```
## Features
- Exposed to assistive technology as a meter via ARIA.
- Labeling support for accessibility.
- Internationalized number formatting as a percentage or value.
## Anatomy
The meter consists of:
- **Meter:** The root container for a meter.
- **Meter.Label:** An accessible label that gives the user information on the meter.
- **Meter.ValueLabel:** The accessible label text representing the current value in a human-readable format.
- **Meter.Track:** The component that visually represents the meter track.
- **Meter.Fill:** The component that visually represents the meter value.
```
Copytsx
<Meter>
<Meter.Label />
<Meter.ValueLabel />
<Meter.Track>
<Meter.Fill />
</Meter.Track>
</Meter>
```
```
Copytsx
<Meter>
<Meter.Label />
<Meter.ValueLabel />
<Meter.Track>
<Meter.Fill />
</Meter.Track>
</Meter>
```
## Example
Battery Level:
80%
index.tsxstyle.css
```
Copytsx
import { Meter } from "@kobalte/core/meter";
import "./style.css";
function App() {
return (
<Meter value={80} class="meter">
<div class="meter__label-container">
<Meter.Label class="meter__label">Battery Level:</Meter.Label>
<Meter.ValueLabel class="meter__value-label" />
</div>
<Meter.Track class="meter__track">
<Meter.Fill class="meter__fill" />
</Meter.Track>
</Meter>
);
}
```
```
Copytsx
import { Meter } from "@kobalte/core/meter";
import "./style.css";
function App() {
return (
<Meter value={80} class="meter">
<div class="meter__label-container">
<Meter.Label class="meter__label">Battery Level:</Meter.Label>
<Meter.ValueLabel class="meter__value-label" />
</div>
<Meter.Track class="meter__track">
<Meter.Fill class="meter__fill" />
</Meter.Track>
</Meter>
);
}
```
## Usage
### Custom value scale
By default, the `value` prop represents the current value of meter, as the minimum and maximum values default to 0 and 100, respectively. Alternatively, a different scale can be used by setting the `minValue` and `maxValue` props.
Disk Space Usage:
40%
```
Copytsx
<Meter value={100} minValue={0} maxValue={250} class="meter">
<div class="meter__label-container">
<Meter.Label class="meter__label">Disk Space Usage:</Meter.Label>
<Meter.ValueLabel class="meter__value-label" />
</div>
<Meter.Track class="meter__track">
<Meter.Fill class="meter__fill" />
</Meter.Track>
</Meter>
```
```
Copytsx
<Meter value={100} minValue={0} maxValue={250} class="meter">
<div class="meter__label-container">
<Meter.Label class="meter__label">Disk Space Usage:</Meter.Label>
<Meter.ValueLabel class="meter__value-label" />
</div>
<Meter.Track class="meter__track">
<Meter.Fill class="meter__fill" />
</Meter.Track>
</Meter>
```
### Custom value label
The `getValueLabel` prop allows the formatted value used in `Meter.ValueLabel` and ARIA to be replaced with a custom string. It receives the current value, min and max values as parameters.
Processing...
3 of 10 tasks completed
```
Copytsx
<Meter
value={3}
minValue={0}
maxValue={10}
getValueLabel={({ value, max }) => `${value} of ${max} tasks completed`}
class="meter"
>
<div class="meter__label-container">
<Meter.Label class="meter__label">Processing...</Meter.Label>
<Meter.ValueLabel class="meter__value-label" />
</div>
<Meter.Track class="meter__track">
<Meter.Fill class="meter__fill" />
</Meter.Track>
</Meter>
```
```
Copytsx
<Meter
value={3}
minValue={0}
maxValue={10}
getValueLabel={({ value, max }) => `${value} of ${max} tasks completed`}
class="meter"
>
<div class="meter__label-container">
<Meter.Label class="meter__label">Processing...</Meter.Label>
<Meter.ValueLabel class="meter__value-label" />
</div>
<Meter.Track class="meter__track">
<Meter.Fill class="meter__fill" />
</Meter.Track>
</Meter>
```
### Meter fill width
We expose a CSS custom property `--kb-meter-fill-width` which corresponds to the percentage of meterion (ex: 80%). If you are building a linear meter, you can use it to set the width of the `Meter.Fill` component in CSS.
## API Reference
### Meter
`Meter` is equivalent to the `Root` import from `@kobalte/core/meter`.
| Prop | Description |
| --- | --- |
| value | `number`<br> The meter value. |
| minValue | `number`<br> The minimum meter value. |
| maxValue | `number`<br> The maximum meter value. |
| getValueLabel | `(params: { value: number; min: number; max: number }) => string`<br> A function to get the accessible label text representing the current value in a human-readable format. If not provided, the value label will be read as a percentage of the max value. |
| Data attribute | Description |
| --- | --- |
`Meter.Label`, `Meter.ValueLabel`, `Meter.Track` and `Meter.Fill` shares the same data-attributes.
## Rendered elements
| Component | Default rendered element |
| --- | --- |
| `Meter` | `div` |
| `Meter.Label` | `span` |
| `Meter.ValueLabel` | `div` |
| `Meter.Track` | `div` |
| `Meter.Fill` | `div` |
Previous[←Menubar](https://kobalte.dev/docs/core/components/menubar)Next[Navigation Menu→](https://kobalte.dev/docs/core/components/navigation-menu)

View File

@ -0,0 +1,549 @@
# Number Field
A number input that allow users to input custom number entries with a keyboard.
## Import
```
Copyts
import { NumberField } from "@kobalte/core/number-field";
// or
import { Root, Label, ... } from "@kobalte/core/number-field";
// or (deprecated)
import { NumberField } from "@kobalte/core";
```
```
Copyts
import { NumberField } from "@kobalte/core/number-field";
// or
import { Root, Label, ... } from "@kobalte/core/number-field";
// or (deprecated)
import { NumberField } from "@kobalte/core";
```
## Features
- Follows the [WAI ARIA Spinbutton](https://www.w3.org/WAI/ARIA/apg/patterns/spinbutton/) design pattern.
- Built with a native `<input>` element.
- Visual and ARIA labeling support.
- Required and invalid states exposed to assistive technology via ARIA.
- Support for description and error message help text linked to the input via ARIA.
- Syncs with form reset events.
- Can be controlled or uncontrolled.
- Supports increment and decrement buttons.
- Format and localize input number and raw input.
- Supports mouse wheel event and all keyboard events.
- Supports browser autofill.
## Anatomy
The number field consists of:
- **NumberField**: The root container for the number field.
- **NumberField.Label**: The label that gives the user information on the number field.
- **NumberField.Input**: The native HTML input of the number field, used for display number.
- **NumberField.HiddenInput**: The native HTML input of the number field, used for raw number form submition.
- **NumberField.IncrementTrigger**: The increment button of the number field.
- **NumberField.DecrementTrigger**: The increment button of the number field.
- **NumberField.Description**: The description that gives the user more information on the number field.
- **NumberField.ErrorMessage**: The error message that gives the user information about how to fix a validation error on number field.
```
Copytsx
<NumberField>
<NumberField.Label />
<NumberField.Input />
<NumberField.HiddenInput />
<NumberField.IncrementTrigger />
<NumberField.DecrementTrigger />
<NumberField.Description />
<NumberField.ErrorMessage />
</NumberField>
```
```
Copytsx
<NumberField>
<NumberField.Label />
<NumberField.Input />
<NumberField.HiddenInput />
<NumberField.IncrementTrigger />
<NumberField.DecrementTrigger />
<NumberField.Description />
<NumberField.ErrorMessage />
</NumberField>
```
## Example
Quantity
ArrowArrow
index.tsxstyle.css
```
Copytsx
import { NumberField } from "@kobalte/core/number-field";
import "./style.css";
function App() {
return (
<NumberField class="number-field">
<NumberField.Label class="number-field__label">
Quantity
</NumberField.Label>
<div class="number-field__group">
<NumberField.Input class="number-field__input" />
<NumberField.IncrementTrigger aria-label="Increment" class="number-field__increment"><ArrowUpIcon/></NumberField.IncrementTrigger>
<NumberField.DecrementTrigger aria-label="Decrement" class="number-field__decrement"><ArrowDownIcon/></NumberField.DecrementTrigger>
</div>
</NumberField>
);
}
```
```
Copytsx
import { NumberField } from "@kobalte/core/number-field";
import "./style.css";
function App() {
return (
<NumberField class="number-field">
<NumberField.Label class="number-field__label">
Quantity
</NumberField.Label>
<div class="number-field__group">
<NumberField.Input class="number-field__input" />
<NumberField.IncrementTrigger aria-label="Increment" class="number-field__increment"><ArrowUpIcon/></NumberField.IncrementTrigger>
<NumberField.DecrementTrigger aria-label="Decrement" class="number-field__decrement"><ArrowDownIcon/></NumberField.DecrementTrigger>
</div>
</NumberField>
);
}
```
## Usage
### Default value
An initial, uncontrolled value can be provided using the `defaultValue` prop.
Quantity
ArrowArrow
```
Copytsx
<NumberField defaultValue={40}>
<NumberField.Label>Quantity</NumberField.Label>
<div>
<NumberField.Input />
<NumberField.IncrementTrigger />
<NumberField.DecrementTrigger />
</div>
</NumberField>
```
```
Copytsx
<NumberField defaultValue={40}>
<NumberField.Label>Quantity</NumberField.Label>
<div>
<NumberField.Input />
<NumberField.IncrementTrigger />
<NumberField.DecrementTrigger />
</div>
</NumberField>
```
### Controlled value
The `value` and `rawValue` props can be used to make the value controlled.
It is recommended to only use the `rawValue` as it is of type `number`.
The `onChange` event is fired when the user type into the input and receive the new value.
The `onRawValueChange` prop is called when the value changes and receives a `number`.
Quantity
ArrowArrow
Quantity: 40. Raw: 40.
```
Copytsx
import { createSignal } from "solid-js";
function ControlledExample() {
const [value, setValue] = createSignal("40");
const [rawValue, setRawValue] = createSignal<number>();
return (
<>
<NumberField
value={value()}
onChange={setValue}
rawValue={rawValue()}
onRawValueChange={setRawValue}
>
<NumberField.Label>Quantity</NumberField.Label>
<div>
<NumberField.Input />
<NumberField.IncrementTrigger />
<NumberField.DecrementTrigger />
</div>
</NumberField>
<p>
Quantity: {value()}. Raw: {rawValue()}.
</p>
</>
);
}
```
```
Copytsx
import { createSignal } from "solid-js";
function ControlledExample() {
const [value, setValue] = createSignal("40");
const [rawValue, setRawValue] = createSignal<number>();
return (
<>
<NumberField
value={value()}
onChange={setValue}
rawValue={rawValue()}
onRawValueChange={setRawValue}
>
<NumberField.Label>Quantity</NumberField.Label>
<div>
<NumberField.Input />
<NumberField.IncrementTrigger />
<NumberField.DecrementTrigger />
</div>
</NumberField>
<p>
Quantity: {value()}. Raw: {rawValue()}.
</p>
</>
);
}
```
### Description
The `NumberField.Description` component can be used to associate additional help text with a number field.
Quantity
ArrowArrow
Choose a quantity.
```
Copytsx
<NumberField>
<NumberField.Label>Quantity</NumberField.Label>
<div>
<NumberField.Input />
<NumberField.IncrementTrigger />
<NumberField.DecrementTrigger />
</div>
<NumberField.Description>Choose a quantity.</NumberField.Description>
</NumberField>
```
```
Copytsx
<NumberField>
<NumberField.Label>Quantity</NumberField.Label>
<div>
<NumberField.Input />
<NumberField.IncrementTrigger />
<NumberField.DecrementTrigger />
</div>
<NumberField.Description>Choose a quantity.</NumberField.Description>
</NumberField>
```
### Error message
The `NumberField.ErrorMessage` component can be used to help the user fix a validation error. It should be combined with the `validationState` prop to semantically mark the text field as invalid for assistive technologies.
By default, it will render only when the `validationState` prop is set to `invalid`, use the `forceMount` prop to always render the error message (ex: for usage with animation libraries).
Quantity
ArrowArrow
Hmm, I prefer 40.
```
Copytsx
import { createSignal } from "solid-js";
function ErrorMessageExample() {
const [rawValue, setRawValue] = createSignal<number>();
return (
<NumberField
onRawValueChange={setRawValue}
validationState={rawValue() !== 40 ? "invalid" : "valid"}
>
<NumberField.Label>Quantity</NumberField.Label>
<div>
<NumberField.Input />
<NumberField.IncrementTrigger />
<NumberField.DecrementTrigger />
</div>
<NumberField.ErrorMessage>Hmm, I prefer 40.</NumberField.ErrorMessage>
</NumberField>
);
}
```
```
Copytsx
import { createSignal } from "solid-js";
function ErrorMessageExample() {
const [rawValue, setRawValue] = createSignal<number>();
return (
<NumberField
onRawValueChange={setRawValue}
validationState={rawValue() !== 40 ? "invalid" : "valid"}
>
<NumberField.Label>Quantity</NumberField.Label>
<div>
<NumberField.Input />
<NumberField.IncrementTrigger />
<NumberField.DecrementTrigger />
</div>
<NumberField.ErrorMessage>Hmm, I prefer 40.</NumberField.ErrorMessage>
</NumberField>
);
}
```
### HTML forms
The number field `name` prop along with `<NumberField.HiddenInput/>` can be used for integration with HTML forms. Only the raw value is passed to the form.
If the formatted value is wanted (unrecommended) set the `name` attribute on `<NumberField.Input/>`.
Quantity
ArrowArrow
ResetSubmit
```
Copytsx
function HTMLFormExample() {
const onSubmit = (e: SubmitEvent) => {
// handle form submission.
};
return (
<form onSubmit={onSubmit}>
<NumberField name="quantity">
<NumberField.Label>Quantity</NumberField.Label>
<NumberField.HiddenInput />
<div>
<NumberField.Input />
<NumberField.IncrementTrigger />
<NumberField.DecrementTrigger />
</div>
</NumberField>
<div>
<button type="reset">Reset</button>
<button type="submit">Submit</button>
</div>
</form>
);
}
```
```
Copytsx
function HTMLFormExample() {
const onSubmit = (e: SubmitEvent) => {
// handle form submission.
};
return (
<form onSubmit={onSubmit}>
<NumberField name="quantity">
<NumberField.Label>Quantity</NumberField.Label>
<NumberField.HiddenInput />
<div>
<NumberField.Input />
<NumberField.IncrementTrigger />
<NumberField.DecrementTrigger />
</div>
</NumberField>
<div>
<button type="reset">Reset</button>
<button type="submit">Submit</button>
</div>
</form>
);
}
```
### Triggers
The number field supports optional increment/decrement triggers that are easily customizable.
Quantity
-+
```
Copytsx
<NumberField>
<NumberField.Label>Quantity</NumberField.Label>
<div>
<NumberField.DecrementTrigger class="custom-trigger">-</NumberField.DecrementTrigger>
<NumberField.Input />
<NumberField.IncrementTrigger class="custom-trigger">+</NumberField.IncrementTrigger>
</div>
</NumberField>
```
```
Copytsx
<NumberField>
<NumberField.Label>Quantity</NumberField.Label>
<div>
<NumberField.DecrementTrigger class="custom-trigger">-</NumberField.DecrementTrigger>
<NumberField.Input />
<NumberField.IncrementTrigger class="custom-trigger">+</NumberField.IncrementTrigger>
</div>
</NumberField>
```
### Format
The value of the number field component can be formatted based on the [locale with the `I18NProvider`](https://kobalte.dev/docs/core/components/i18n-provider) and `formatOptions`. For more information see [React Spectrum NumberField](https://react-spectrum.adobe.com/react-spectrum/NumberField.html).
Price
ArrowArrow
```
Copytsx
<NumberField formatOptions={{ style: "currency", currency: "USD" }} defaultValue={4}>
<NumberField.Label>Price</NumberField.Label>
<div>
<NumberField.Input />
<NumberField.IncrementTrigger />
<NumberField.DecrementTrigger />
</div>
</NumberField>
```
```
Copytsx
<NumberField formatOptions={{ style: "currency", currency: "USD" }} defaultValue={4}>
<NumberField.Label>Price</NumberField.Label>
<div>
<NumberField.Input />
<NumberField.IncrementTrigger />
<NumberField.DecrementTrigger />
</div>
</NumberField>
```
### Autofill
The number field supports autofill through `NumberField.HiddenInput`.
```
Copytsx
<NumberField>
<NumberField.Label>Quantity</NumberField.Label>
<NumberField.HiddenInput />
<div>
<NumberField.Input />
<NumberField.IncrementTrigger />
<NumberField.DecrementTrigger />
</div>
</NumberField>
```
```
Copytsx
<NumberField>
<NumberField.Label>Quantity</NumberField.Label>
<NumberField.HiddenInput />
<div>
<NumberField.Input />
<NumberField.IncrementTrigger />
<NumberField.DecrementTrigger />
</div>
</NumberField>
```
## API Reference
### NumberField
`NumberField` is equivalent to the `Root` import from `@kobalte/core/number-field` (and deprecated `NumberField.Root`).
| Prop | Description |
| --- | --- |
| value | `string | number`<br> The controlled formatted value of the number field. |
| defaultValue | `string | number`<br> The default value when initially rendered. Useful when you do not need to control the value. |
| onChange | `(value: string) => void`<br> Event handler called when the value of the NumberField changes as a formatted value. |
| rawValue | `number`<br> The controlled raw value of the number field. |
| onRawValueChange | `(value: number) => void`<br> Event handler called when the value of the NumberField changes as a number. |
| minValue | `number`<br> The smallest value allowed in the number field, defaults to `Number.MIN_SAFE_INTEGER`. |
| maxValue | `number`<br> The largest value allowed in the number field, defaults to `Number.MAX_SAFE_INTEGER`. |
| step | `number`<br> Increment/Decrement step when using the triggers or the arrows on keyboard in the number field. |
| largeStep | `number`<br> Increment/Decrement step when using the Page UP/Down keys in the number field, defaults `10 * step`. |
| changeOnWheel | `boolean`<br> Whether to increment/decrement on wheel scroll inside the number field. |
| format | `boolean`<br> Whether to format the input value. |
| formatOptions | [`Intl.NumberFormatOptions`](https://github.com/microsoft/TypeScript/blob/353ccb7688351ae33ccf6e0acb913aa30621eaf4/src/lib/es2020.intl.d.ts#L243-L251)<br> Formating options for the value of the number field. |
| allowedInput | `RegExp`<br> Allowed input characters in the number field (only prevents onInput, not paste), defaults to locale and format characters. |
| name | `string`<br> The name of the NumberField.HiddenInput of the number field, used when submitting an HTML form. See [MDN](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#htmlattrdefname). |
| validationState | `'valid' | 'invalid'`<br> Whether the number field should display its "valid" or "invalid" visual styling. |
| required | `boolean`<br> Whether the user must fill the number field before the owning form can be submitted. |
| disabled | `boolean`<br> Whether the number field is disabled. |
| readOnly | `boolean`<br> Whether the number field items can be selected but not changed by the user. |
| Data attribute | Description |
| --- | --- |
| data-valid | Present when the number field is valid according to the validation rules. |
| data-invalid | Present when the number field is invalid according to the validation rules. |
| data-required | Present when the user must fill the number field before the owning form can be submitted. |
| data-disabled | Present when the number field is disabled. |
| data-readonly | Present when the number field is read only. |
`NumberField.Label`, `NumberField.Input`, `NumberField.HiddenInput`, `NumberField.Description` and `NumberField.ErrorMesssage` share the same data-attributes.
### NumberField.ErrorMessage
| Prop | Description |
| --- | --- |
| forceMount | `boolean`<br> Used to force mounting when more control is needed. Useful when controlling animation with SolidJS animation libraries. |
## Rendered elements
| Component | Default rendered element |
| --- | --- |
| `NumberField` | `div` |
| `NumberField.Label` | `label` |
| `NumberField.Input` | `input` |
| `NumberField.HiddenInput` | `input` |
| `NumberField.IncrementTrigger` | `button` |
| `NumberField.DecrementTrigger` | `button` |
| `NumberField.Description` | `div` |
| `NumberField.ErrorMessage` | `div` |
Previous[←Navigation Menu](https://kobalte.dev/docs/core/components/navigation-menu)Next[Pagination→](https://kobalte.dev/docs/core/components/pagination)

View File

@ -0,0 +1,368 @@
# Pagination
Allows the user to select a specific page from a range of pages.
## Import
```
Copyts
import { Pagination } from "@kobalte/core/pagination";
// or
import { Root, Item, ... } from "@kobalte/core/pagination";
// or (deprecated)
import { Pagination } from "@kobalte/core";
```
```
Copyts
import { Pagination } from "@kobalte/core/pagination";
// or
import { Root, Item, ... } from "@kobalte/core/pagination";
// or (deprecated)
import { Pagination } from "@kobalte/core";
```
## Features
- Labeling support for accessibility.
- Tab focus management.
- Can be controlled or uncontrolled.
- Customizable appearance.
## Anatomy
The pagination consists of:
- **Pagination:** The root container for the pagination component.
- **Pagination.Item:** An item of the pagination.
- **Pagination.Ellipsis:** Ellipsis item of the pagination.
- **Pagination.Previous:** Previous button of the pagination.
- **Pagination.Next:** Next button of the pagination.
- **Pagination.Items:** Contains the list of items and allows a user to select one of them.
```
Copytsx
<Pagination>
<Pagination.Previous/>
<Pagination.Items/>
<Pagination.Next/>
</Select>
```
```
Copytsx
<Pagination>
<Pagination.Previous/>
<Pagination.Items/>
<Pagination.Next/>
</Select>
```
## Example
index.tsxstyle.css
```
Copytsx
import { Pagination } from "@kobalte/core/pagination";
import "./style.css";
function App() {
return (
<Pagination
class="pagination__root"
count={10}
itemComponent={props => (
<Pagination.Item class="pagination__item" page={props.page}>{props.page}</Pagination.Item>
)}
ellipsisComponent={() => (
<Pagination.Ellipsis class="pagination__ellipsis">...</Pagination.Ellipsis>
)}
>
<Pagination.Previous class="pagination__item">Previous</Pagination.Previous>
<Pagination.Items/>
<Pagination.Next class="pagination__item">Next</Pagination.Next>
</Pagination>
);
}
```
```
Copytsx
import { Pagination } from "@kobalte/core/pagination";
import "./style.css";
function App() {
return (
<Pagination
class="pagination__root"
count={10}
itemComponent={props => (
<Pagination.Item class="pagination__item" page={props.page}>{props.page}</Pagination.Item>
)}
ellipsisComponent={() => (
<Pagination.Ellipsis class="pagination__ellipsis">...</Pagination.Ellipsis>
)}
>
<Pagination.Previous class="pagination__item">Previous</Pagination.Previous>
<Pagination.Items/>
<Pagination.Next class="pagination__item">Next</Pagination.Next>
</Pagination>
);
}
```
## Usage
### Default value
An initial, uncontrolled page can be provided using the `defaultPage` prop, which accepts a number smaller or equal to the `count` and starts at 1.
```
Copytsx
<Pagination
count={10}
defaultPage={4}
itemComponent={props => <Pagination.Item page={props.page}>{props.page}</Pagination.Item>}
ellipsisComponent={() => <Pagination.Ellipsis>...</Pagination.Ellipsis>}
>
<Pagination.Previous>Previous</Pagination.Previous>
<Pagination.Items />
<Pagination.Next>Next</Pagination.Next>
</Pagination>
```
```
Copytsx
<Pagination
count={10}
defaultPage={4}
itemComponent={props => <Pagination.Item page={props.page}>{props.page}</Pagination.Item>}
ellipsisComponent={() => <Pagination.Ellipsis>...</Pagination.Ellipsis>}
>
<Pagination.Previous>Previous</Pagination.Previous>
<Pagination.Items />
<Pagination.Next>Next</Pagination.Next>
</Pagination>
```
### Controlled value
The `page` prop, which accepts a page number, can be used to make the value controlled. The `onPageChange` event is fired when the user selects an item, and receives the new page number.
```
Copytsx
import { createSignal } from "solid-js";
export function ControlledExample() {
const [page, setPage] = createSignal(4);
return (
<Pagination
page={page()}
onPageChange={setPage}
count={10}
itemComponent={props => <Pagination.Item page={props.page}>{props.page}</Pagination.Item>}
ellipsisComponent={() => <Pagination.Ellipsis>...</Pagination.Ellipsis>}
>
<Pagination.Previous>Previous</Pagination.Previous>
<Pagination.Items />
<Pagination.Next>Next</Pagination.Next>
</Pagination>
);
}
```
```
Copytsx
import { createSignal } from "solid-js";
export function ControlledExample() {
const [page, setPage] = createSignal(4);
return (
<Pagination
page={page()}
onPageChange={setPage}
count={10}
itemComponent={props => <Pagination.Item page={props.page}>{props.page}</Pagination.Item>}
ellipsisComponent={() => <Pagination.Ellipsis>...</Pagination.Ellipsis>}
>
<Pagination.Previous>Previous</Pagination.Previous>
<Pagination.Items />
<Pagination.Next>Next</Pagination.Next>
</Pagination>
);
}
```
### Next / Previous buttons example
The appearance can be customized by omitting the Next and Previous Button.
```
Copytsx
<Pagination
count={10}
itemComponent={props => <Pagination.Item page={props.page}>{props.page}</Pagination.Item>}
ellipsisComponent={() => <Pagination.Ellipsis>...</Pagination.Ellipsis>}
>
<Pagination.Items />
</Pagination>
```
```
Copytsx
<Pagination
count={10}
itemComponent={props => <Pagination.Item page={props.page}>{props.page}</Pagination.Item>}
ellipsisComponent={() => <Pagination.Ellipsis>...</Pagination.Ellipsis>}
>
<Pagination.Items />
</Pagination>
```
### First / Last item example
The First and Last item can be hidden instead of displaying at all times.
```
Copytsx
<Pagination
count={10}
showFirst={false}
showLast={false}
itemComponent={props => <Pagination.Item page={props.page}>{props.page}</Pagination.Item>}
ellipsisComponent={() => <Pagination.Ellipsis>...</Pagination.Ellipsis>}
>
<Pagination.Previous>Previous</Pagination.Previous>
<Pagination.Items />
<Pagination.Next>Next</Pagination.Next>
</Pagination>
```
```
Copytsx
<Pagination
count={10}
showFirst={false}
showLast={false}
itemComponent={props => <Pagination.Item page={props.page}>{props.page}</Pagination.Item>}
ellipsisComponent={() => <Pagination.Ellipsis>...</Pagination.Ellipsis>}
>
<Pagination.Previous>Previous</Pagination.Previous>
<Pagination.Items />
<Pagination.Next>Next</Pagination.Next>
</Pagination>
```
### Siblings example
The number of items around the current page item can be customized.
```
Copytsx
<Pagination
count={10}
siblingCount={2}
itemComponent={props => <Pagination.Item page={props.page}>{props.page}</Pagination.Item>}
ellipsisComponent={() => <Pagination.Ellipsis>...</Pagination.Ellipsis>}
>
<Pagination.Previous>Previous</Pagination.Previous>
<Pagination.Items />
<Pagination.Next>Next</Pagination.Next>
</Pagination>
```
```
Copytsx
<Pagination
count={10}
siblingCount={2}
itemComponent={props => <Pagination.Item page={props.page}>{props.page}</Pagination.Item>}
ellipsisComponent={() => <Pagination.Ellipsis>...</Pagination.Ellipsis>}
>
<Pagination.Previous>Previous</Pagination.Previous>
<Pagination.Items />
<Pagination.Next>Next</Pagination.Next>
</Pagination>
```
### Fixed Items example
The total number of items can be fixed to avoid content shift. If ellipsis are disabled (by returning an empty component) use `fixedItems="no-ellipsis"` instead.
```
Copytsx
<Pagination
count={10}
fixedItems
itemComponent={props => <Pagination.Item page={props.page}>{props.page}</Pagination.Item>}
ellipsisComponent={() => <Pagination.Ellipsis>...</Pagination.Ellipsis>}
>
<Pagination.Previous>Previous</Pagination.Previous>
<Pagination.Items />
<Pagination.Next>Next</Pagination.Next>
</Pagination>
```
```
Copytsx
<Pagination
count={10}
fixedItems
itemComponent={props => <Pagination.Item page={props.page}>{props.page}</Pagination.Item>}
ellipsisComponent={() => <Pagination.Ellipsis>...</Pagination.Ellipsis>}
>
<Pagination.Previous>Previous</Pagination.Previous>
<Pagination.Items />
<Pagination.Next>Next</Pagination.Next>
</Pagination>
```
## API Reference
### Pagination
`Pagination` is equivalent to the `Root` import from `@kobalte/core/pagination` (and deprecated `Pagination.Root`).
| Prop | Description |
| --- | --- |
| page | `number`<br> The controlled page number of the pagination. (1-indexed) |
| defaultPage | `string`<br> The default page number when initially rendered. (1-indexed) |
| onPageChange | `(page: number) => void`<br> Event handler called when the page number changes. |
| count | `number`<br> The number of pages for the pagination. |
| siblingCount | `number`<br> The number of siblings to show around the current page item. |
| showFirst | `boolean`<br> Whether to always show the first page item. |
| showLast | `boolean`<br> Whether to always show the last page item. |
| fixedItems | `boolean | "no-ellipsis"`<br> Whether to always show the same number of items (to avoid content shift). Special value: `"no-ellipsis"` does not count the ellipsis as an item (used when ellipsis are disabled). |
| itemComponent | `Component<{page: number}>`<br> The component to render as an item in the `Pagination.Items`. |
| ellipsisComponent | `Component`<br> The component to render as an ellipsis item in the `Pagination.Items`. |
| disabled | `boolean`<br> Whether the pagination is disabled. |
| Data attribute | Description |
| --- | --- |
| data-disabled | Present when the pagination is disabled. |
### Pagination.Item
| Prop | Description |
| --- | --- |
| page | `number`<br> The page number to render. |
| Data attribute | Description |
| --- | --- |
| data-current | Present when the item is the current page. |
## Rendered elements
| Component | Default rendered element |
| --- | --- |
| `Pagination` | `div` |
| `Pagination.Item` | `button` |
| `Pagination.Ellipsis` | `div` |
| `Pagination.Previous` | `button` |
| `Pagination.Next` | `button` |
| `Pagination.Items` | none |
Previous[←Number Field](https://kobalte.dev/docs/core/components/number-field)Next[Popover→](https://kobalte.dev/docs/core/components/popover)

View File

@ -0,0 +1,230 @@
# Progress
Show either determinate or indeterminate progress of an operation over time.
## Import
```
Copyts
import { Progress } from "@kobalte/core/progress";
// or
import { Root, Label, ... } from "@kobalte/core/progress";
// or (deprecated)
import { Progress } from "@kobalte/core";
```
```
Copyts
import { Progress } from "@kobalte/core/progress";
// or
import { Root, Label, ... } from "@kobalte/core/progress";
// or (deprecated)
import { Progress } from "@kobalte/core";
```
## Features
- Exposed to assistive technology as a progress bar via ARIA.
- Labeling support for accessibility.
- Internationalized number formatting as a percentage or value.
- Determinate and indeterminate progress support.
## Anatomy
The progress consists of:
- **Progress:** The root container for a progress bar.
- **Progress.Label:** An accessible label that gives the user information on the progress.
- **Progress.ValueLabel:** The accessible label text representing the current value in a human-readable format.
- **Progress.Track:** The component that visually represents the progress track.
- **Progress.Fill:** The component that visually represents the progress value.
```
Copytsx
<Progress>
<Progress.Label />
<Progress.ValueLabel />
<Progress.Track>
<Progress.Fill />
</Progress.Track>
</Progress>
```
```
Copytsx
<Progress>
<Progress.Label />
<Progress.ValueLabel />
<Progress.Track>
<Progress.Fill />
</Progress.Track>
</Progress>
```
## Example
Loading...
80%
index.tsxstyle.css
```
Copytsx
import { Progress } from "@kobalte/core/progress";
import "./style.css";
function App() {
return (
<Progress value={80} class="progress">
<div class="progress__label-container">
<Progress.Label class="progress__label">Loading...</Progress.Label>
<Progress.ValueLabel class="progress__value-label" />
</div>
<Progress.Track class="progress__track">
<Progress.Fill class="progress__fill" />
</Progress.Track>
</Progress>
);
}
```
```
Copytsx
import { Progress } from "@kobalte/core/progress";
import "./style.css";
function App() {
return (
<Progress value={80} class="progress">
<div class="progress__label-container">
<Progress.Label class="progress__label">Loading...</Progress.Label>
<Progress.ValueLabel class="progress__value-label" />
</div>
<Progress.Track class="progress__track">
<Progress.Fill class="progress__fill" />
</Progress.Track>
</Progress>
);
}
```
## Usage
### Custom value scale
By default, the `value` prop represents the current percentage of progress, as the minimum and maximum values default to 0 and 100, respectively. Alternatively, a different scale can be used by setting the `minValue` and `maxValue` props.
Loading...
50%
```
Copytsx
<Progress value={100} minValue={50} maxValue={150} class="progress">
<div class="progress__label-container">
<Progress.Label class="progress__label">Loading...</Progress.Label>
<Progress.ValueLabel class="progress__value-label" />
</div>
<Progress.Track class="progress__track">
<Progress.Fill class="progress__fill" />
</Progress.Track>
</Progress>
```
```
Copytsx
<Progress value={100} minValue={50} maxValue={150} class="progress">
<div class="progress__label-container">
<Progress.Label class="progress__label">Loading...</Progress.Label>
<Progress.ValueLabel class="progress__value-label" />
</div>
<Progress.Track class="progress__track">
<Progress.Fill class="progress__fill" />
</Progress.Track>
</Progress>
```
### Custom value label
The `getValueLabel` prop allows the formatted value used in `Progress.ValueLabel` and ARIA to be replaced with a custom string. It receives the current value, min and max values as parameters.
Processing...
3 of 10 tasks completed
```
Copytsx
<Progress
value={3}
minValue={0}
maxValue={10}
getValueLabel={({ value, max }) => `${value} of ${max} tasks completed`}
class="progress"
>
<div class="progress__label-container">
<Progress.Label class="progress__label">Processing...</Progress.Label>
<Progress.ValueLabel class="progress__value-label" />
</div>
<Progress.Track class="progress__track">
<Progress.Fill class="progress__fill" />
</Progress.Track>
</Progress>
```
```
Copytsx
<Progress
value={3}
minValue={0}
maxValue={10}
getValueLabel={({ value, max }) => `${value} of ${max} tasks completed`}
class="progress"
>
<div class="progress__label-container">
<Progress.Label class="progress__label">Processing...</Progress.Label>
<Progress.ValueLabel class="progress__value-label" />
</div>
<Progress.Track class="progress__track">
<Progress.Fill class="progress__fill" />
</Progress.Track>
</Progress>
```
### Progress bar fill width
We expose a CSS custom property `--kb-progress-fill-width` which corresponds to the percentage of progression (ex: 80%). If you are building a linear progress bar, you can use it to set the width of the `Progress.Fill` component in CSS.
## API Reference
### Progress
`Progress` is equivalent to the `Root` import from `@kobalte/core/progress` (and deprecated `Progress.Root`).
| Prop | Description |
| --- | --- |
| value | `number`<br> The progress value. |
| minValue | `number`<br> The minimum progress value. |
| maxValue | `number`<br> The maximum progress value. |
| indeterminate | `boolean`<br> Whether the progress is in an indeterminate state. |
| getValueLabel | `(params: { value: number; min: number; max: number }) => string`<br> A function to get the accessible label text representing the current value in a human-readable format. If not provided, the value label will be read as a percentage of the max value. |
| Data attribute | Description |
| --- | --- |
| data-progress='loading' | Present when the progress is not complete (`value` < `maxValue`). |
| data-progress='complete' | Present when the progress is complete (`value` === `maxValue`). |
| data-indeterminate | Present when the progress is in an indeterminate state. |
`Progress.Label`, `Progress.ValueLabel`, `Progress.Track` and `Progress.Fill` shares the same data-attributes.
## Rendered elements
| Component | Default rendered element |
| --- | --- |
| `Progress` | `div` |
| `Progress.Label` | `span` |
| `Progress.ValueLabel` | `div` |
| `Progress.Track` | `div` |
| `Progress.Fill` | `div` |
Previous[←Popover](https://kobalte.dev/docs/core/components/popover)Next[Radio Group→](https://kobalte.dev/docs/core/components/radio-group)

View File

@ -0,0 +1,507 @@
# Radio Group
A set of checkable buttons, known as radio buttons, where no more than one of the buttons can be checked at a time.
## Import
```
Copyts
import { RadioGroup } from "@kobalte/core/radio-group";
// or
import { Root, Label, ... } from "@kobalte/core/radio-group";
// or (deprecated)
import { RadioGroup } from "@kobalte/core";
```
```
Copyts
import { RadioGroup } from "@kobalte/core/radio-group";
// or
import { Root, Label, ... } from "@kobalte/core/radio-group";
// or (deprecated)
import { RadioGroup } from "@kobalte/core";
```
## Features
- Follow the [WAI ARIA Radio Group](https://www.w3.org/WAI/ARIA/apg/patterns/radiobutton/) design pattern.
- Each radio is built with a native HTML `<input>` element, which is visually hidden to allow custom styling.
- Syncs with form reset events.
- Group and radio labeling support for assistive technology.
- Can be controlled or uncontrolled.
## Anatomy
The radio group consists of:
- **RadioGroup**: The root container for the radio group.
- **RadioGroup.Label**: The label that gives the user information on the radio group.
- **RadioGroup.Description**: The description that gives the user more information on the radio group.
- **RadioGroup.ErrorMessage**: The error message that gives the user information about how to fix a validation error on the radio group.
The radio item consists of:
- **RadioGroup.Item**: The root container for a radio button.
- **RadioGroup.ItemInput**: The native html input that is visually hidden in the radio button.
- **RadioGroup.ItemControl**: The element that visually represents a radio button.
- **RadioGroup.ItemIndicator**: The visual indicator rendered when the radio button is in a checked state.
- **RadioGroup.ItemLabel**: The label that gives the user information on the radio button.
- **RadioGroup.ItemDescription**: The description that gives the user more information on the radio button.
```
Copytsx
<RadioGroup>
<RadioGroup.Label />
<RadioGroup.Item>
<RadioGroup.ItemInput />
<RadioGroup.ItemControl>
<RadioGroup.ItemIndicator />
</RadioGroup.ItemControl>
<RadioGroup.ItemLabel />
<RadioGroup.ItemDescription />
</RadioGroup.Item>
<RadioGroup.Description />
<RadioGroup.ErrorMessage />
</RadioGroup>
```
```
Copytsx
<RadioGroup>
<RadioGroup.Label />
<RadioGroup.Item>
<RadioGroup.ItemInput />
<RadioGroup.ItemControl>
<RadioGroup.ItemIndicator />
</RadioGroup.ItemControl>
<RadioGroup.ItemLabel />
<RadioGroup.ItemDescription />
</RadioGroup.Item>
<RadioGroup.Description />
<RadioGroup.ErrorMessage />
</RadioGroup>
```
## Example
Favorite fruit
Apple
Orange
Watermelon
index.tsxstyle.css
```
Copytsx
import { RadioGroup } from "@kobalte/core/radio-group";
import "./style.css";
function App() {
return (
<RadioGroup class="radio-group">
<RadioGroup.Label class="radio-group__label">Favorite fruit</RadioGroup.Label>
<div class="radio-group__items" role="presentation">
<For each={["Apple", "Orange", "Watermelon"]}>
{fruit => (
<RadioGroup.Item value={fruit} class="radio">
<RadioGroup.ItemInput class="radio__input" />
<RadioGroup.ItemControl class="radio__control">
<RadioGroup.ItemIndicator class="radio__indicator" />
</RadioGroup.ItemControl>
<RadioGroup.ItemLabel class="radio__label">{fruit}</RadioGroup.ItemLabel>
</RadioGroup.Item>
)}
</For>
</div>
</RadioGroup>
);
}
```
```
Copytsx
import { RadioGroup } from "@kobalte/core/radio-group";
import "./style.css";
function App() {
return (
<RadioGroup class="radio-group">
<RadioGroup.Label class="radio-group__label">Favorite fruit</RadioGroup.Label>
<div class="radio-group__items" role="presentation">
<For each={["Apple", "Orange", "Watermelon"]}>
{fruit => (
<RadioGroup.Item value={fruit} class="radio">
<RadioGroup.ItemInput class="radio__input" />
<RadioGroup.ItemControl class="radio__control">
<RadioGroup.ItemIndicator class="radio__indicator" />
</RadioGroup.ItemControl>
<RadioGroup.ItemLabel class="radio__label">{fruit}</RadioGroup.ItemLabel>
</RadioGroup.Item>
)}
</For>
</div>
</RadioGroup>
);
}
```
## Usage
### Default value
An initial, uncontrolled value can be provided using the `defaultValue` prop, which accepts a value corresponding with the `value` prop of each radio.
Favorite fruit
Apple
Orange
Watermelon
```
Copytsx
<RadioGroup defaultValue="Orange">
<RadioGroup.Label>Favorite fruit</RadioGroup.Label>
<div role="presentation">
<For each={["Apple", "Orange", "Watermelon"]}>
{fruit => <RadioGroup.Item value={fruit}>...</RadioGroup.Item>}
</For>
</div>
</RadioGroup>
```
```
Copytsx
<RadioGroup defaultValue="Orange">
<RadioGroup.Label>Favorite fruit</RadioGroup.Label>
<div role="presentation">
<For each={["Apple", "Orange", "Watermelon"]}>
{fruit => <RadioGroup.Item value={fruit}>...</RadioGroup.Item>}
</For>
</div>
</RadioGroup>
```
The `role="presentation"` is required for all non content elements between the `RadioGroup` and `RadioGroup.Item` due to a bug in Chromium based browsers that incorrectly parse semantics and break screen readers.
### Controlled value
The `value` prop, which accepts a value corresponding with the `value` prop of each radio, can be used to make the value controlled. The `onChange` event is fired when the user selects a radio, and receives the new value.
Favorite fruit
Apple
Orange
Watermelon
Your favorite fruit is: Orange.
```
Copytsx
import { createSignal } from "solid-js";
function ControlledExample() {
const [value, setValue] = createSignal("Orange");
return (
<>
<RadioGroup value={value()} onChange={setValue}>
<RadioGroup.Label>Favorite fruit</RadioGroup.Label>
<div role="presentation">
<For each={["Apple", "Orange", "Watermelon"]}>
{fruit => <RadioGroup.Item value={fruit}>...</RadioGroup.Item>}
</For>
</div>
</RadioGroup>
<p>Your favorite fruit is: {value()}.</p>
</>
);
}
```
```
Copytsx
import { createSignal } from "solid-js";
function ControlledExample() {
const [value, setValue] = createSignal("Orange");
return (
<>
<RadioGroup value={value()} onChange={setValue}>
<RadioGroup.Label>Favorite fruit</RadioGroup.Label>
<div role="presentation">
<For each={["Apple", "Orange", "Watermelon"]}>
{fruit => <RadioGroup.Item value={fruit}>...</RadioGroup.Item>}
</For>
</div>
</RadioGroup>
<p>Your favorite fruit is: {value()}.</p>
</>
);
}
```
### Description
The `RadioGroup.Description` component can be used to associate additional help text with a radio group.
Favorite fruit
Apple
Orange
Watermelon
Choose the fruit you like the most.
```
Copytsx
<RadioGroup>
<RadioGroup.Label>Favorite fruit</RadioGroup.Label>
<div role="presentation">
<For each={["Apple", "Orange", "Watermelon"]}>
{fruit => <RadioGroup.Item value={fruit}>...</RadioGroup.Item>}
</For>
</div>
<RadioGroup.Description>Choose the fruit you like the most.</RadioGroup.Description>
</RadioGroup>
```
```
Copytsx
<RadioGroup>
<RadioGroup.Label>Favorite fruit</RadioGroup.Label>
<div role="presentation">
<For each={["Apple", "Orange", "Watermelon"]}>
{fruit => <RadioGroup.Item value={fruit}>...</RadioGroup.Item>}
</For>
</div>
<RadioGroup.Description>Choose the fruit you like the most.</RadioGroup.Description>
</RadioGroup>
```
### Error message
The `RadioGroup.ErrorMessage` component can be used to help the user fix a validation error. It should be combined with the `validationState` prop to semantically mark the radio group as invalid for assistive technologies.
By default, it will render only when the `validationState` prop is set to `invalid`, use the `forceMount` prop to always render the error message (ex: for usage with animation libraries).
Favorite fruit
Apple
Orange
Watermelon
Hmm, I prefer apples.
```
Copytsx
import { createSignal } from "solid-js";
function ErrorMessageExample() {
const [value, setValue] = createSignal("Orange");
return (
<RadioGroup
value={value()}
onChange={setValue}
validationState={value() !== "Apple" ? "invalid" : "valid"}
>
<RadioGroup.Label>Favorite fruit</RadioGroup.Label>
<div role="presentation">
<For each={["Apple", "Orange", "Watermelon"]}>
{fruit => <RadioGroup.Item value={fruit}>...</RadioGroup.Item>}
</For>
</div>
<RadioGroup.ErrorMessage>Hmm, I prefer apples.</RadioGroup.ErrorMessage>
</RadioGroup>
);
}
```
```
Copytsx
import { createSignal } from "solid-js";
function ErrorMessageExample() {
const [value, setValue] = createSignal("Orange");
return (
<RadioGroup
value={value()}
onChange={setValue}
validationState={value() !== "Apple" ? "invalid" : "valid"}
>
<RadioGroup.Label>Favorite fruit</RadioGroup.Label>
<div role="presentation">
<For each={["Apple", "Orange", "Watermelon"]}>
{fruit => <RadioGroup.Item value={fruit}>...</RadioGroup.Item>}
</For>
</div>
<RadioGroup.ErrorMessage>Hmm, I prefer apples.</RadioGroup.ErrorMessage>
</RadioGroup>
);
}
```
### HTML forms
The radio group `name` prop, paired with the radio `value` prop, can be used for integration with HTML forms.
Favorite fruit
Apple
Orange
Watermelon
ResetSubmit
```
Copytsx
function HTMLFormExample() {
const onSubmit = (e: SubmitEvent) => {
// handle form submission.
};
return (
<form onSubmit={onSubmit}>
<RadioGroup name="favorite-fruit">
<RadioGroup.Label>Favorite fruit</RadioGroup.Label>
<div role="presentation">
<For each={["Apple", "Orange", "Watermelon"]}>
{fruit => <RadioGroup.Item value={fruit}>...</RadioGroup.Item>}
</For>
</div>
</RadioGroup>
<div>
<button type="reset">Reset</button>
<button type="submit">Submit</button>
</div>
</form>
);
}
```
```
Copytsx
function HTMLFormExample() {
const onSubmit = (e: SubmitEvent) => {
// handle form submission.
};
return (
<form onSubmit={onSubmit}>
<RadioGroup name="favorite-fruit">
<RadioGroup.Label>Favorite fruit</RadioGroup.Label>
<div role="presentation">
<For each={["Apple", "Orange", "Watermelon"]}>
{fruit => <RadioGroup.Item value={fruit}>...</RadioGroup.Item>}
</For>
</div>
</RadioGroup>
<div>
<button type="reset">Reset</button>
<button type="submit">Submit</button>
</div>
</form>
);
}
```
## API Reference
### RadioGroup
`RadioGroup` is equivalent to the `Root` import from `@kobalte/core/radio-group` (and deprecated `RadioGroup.Root`).
| Prop | Description |
| --- | --- |
| value | `string`<br> The controlled value of the radio button to check. |
| defaultValue | `string`<br> The value of the radio button that should be checked when initially rendered. Useful when you do not need to control the state of the radio buttons. |
| onChange | `(value: string) => void`<br> Event handler called when the value changes. |
| orientation | `'horizontal' | 'vertical'`<br> The axis the radio group items should align with. |
| name | `string`<br> The name of the radio group. Submitted with its owning form as part of a name/value pair. |
| validationState | `'valid' | 'invalid'`<br> Whether the radio group should display its "valid" or "invalid" visual styling. |
| required | `boolean`<br> Whether the user must check a radio group item before the owning form can be submitted. |
| disabled | `boolean`<br> Whether the radio group is disabled. |
| readOnly | `boolean`<br> Whether the radio group items can be selected but not changed by the user. |
| Data attribute | Description |
| --- | --- |
| data-valid | Present when the radio group is valid according to the validation rules. |
| data-invalid | Present when the radio group is invalid according to the validation rules. |
| data-required | Present when the user must check a radio group item before the owning form can be submitted. |
| data-disabled | Present when the radio group is disabled. |
| data-readonly | Present when the radio group is read only. |
`RadioGroup.Label`, `RadioGroup.Description` and `RadioGroup.ErrorMesssage` shares the same data-attributes.
### RadioGroup.ErrorMessage
| Prop | Description |
| --- | --- |
| forceMount | `boolean`<br> Used to force mounting when more control is needed. Useful when controlling animation with SolidJS animation libraries. |
### RadioGroup.Item
| Prop | Description |
| --- | --- |
| value | `string`<br> The value of the radio button, used when submitting an HTML form. See [MDN](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/radio#Value). |
| disabled | `boolean`<br> Whether the radio button is disabled or not. |
| Data attribute | Description |
| --- | --- |
| data-valid | Present when the parent radio group is valid according to the validation rules. |
| data-invalid | Present when the parent radio group is invalid according to the validation rules. |
| data-checked | Present when the radio is checked. |
| data-disabled | Present when the radio is disabled. |
`RadioGroup.ItemInput`, `RadioGroup.ItemControl`, `RadioGroup.ItemIndicator` and `RadioGroup.ItemLabel` shares the same data-attributes.
### RadioGroup.ItemIndicator
| Prop | Description |
| --- | --- |
| forceMount | `boolean`<br> Used to force mounting when more control is needed. Useful when controlling animation with SolidJS animation libraries. |
## Rendered elements
| Component | Default rendered element |
| --- | --- |
| `RadioGroup` | `div` |
| `RadioGroup.Label` | `span` |
| `RadioGroup.Description` | `div` |
| `RadioGroup.ErrorMessage` | `div` |
| `RadioGroup.Item` | `div` |
| `RadioGroup.ItemInput` | `input` |
| `RadioGroup.ItemControl` | `div` |
| `RadioGroup.ItemIndicator` | `div` |
| `RadioGroup.ItemLabel` | `label` |
| `RadioGroup.ItemDescription` | `div` |
## Accessibility
### Keyboard Interactions
| Key | Description |
| --- | --- |
| `Tab` | Moves focus to either the checked radio button or the first radio button in the group. |
| `Space` | When focus is on an unchecked radio button, checks it. |
| `ArrowDown` | Moves focus and checks the next radio button in the group. |
| `ArrowRight` | Moves focus and checks the next radio button in the group. |
| `ArrowUp` | Moves focus and checks the previous radio button in the group. |
| `ArrowLeft` | Moves focus and checks the previous radio button in the group. |
Previous[←Progress](https://kobalte.dev/docs/core/components/progress)Next[Rating Group→](https://kobalte.dev/docs/core/components/rating-group)

View File

@ -0,0 +1,110 @@
# Separator
Visually or semantically separates content.
## Import
```
Copyts
import { Separator } from "@kobalte/core/separator";
// or
import { Root } from "@kobalte/core/separator";
// or (deprecated)
import { Separator } from "@kobalte/core";
```
```
Copyts
import { Separator } from "@kobalte/core/separator";
// or
import { Root } from "@kobalte/core/separator";
// or (deprecated)
import { Separator } from "@kobalte/core";
```
## Features
- Native HTML `<hr>` element support.
- Custom element type support via the [WAI ARIA Separator](https://w3c.github.io/aria/#separator) role.
- Support for horizontal and vertical orientation.
## Anatomy
The separator consists of:
- **Separator:** The root container for a separator.
```
Copytsx
<Separator />
```
```
Copytsx
<Separator />
```
## Example
Content above
* * *
Content below
index.tsxstyle.css
```
Copytsx
import { Separator } from "@kobalte/core/separator";
import "./style.css";
function App() {
return (
<div>
<span>Content above</span>
<Separator class="separator" />
<span>Content below</span>
</div>
);
}
```
```
Copytsx
import { Separator } from "@kobalte/core/separator";
import "./style.css";
function App() {
return (
<div>
<span>Content above</span>
<Separator class="separator" />
<span>Content below</span>
</div>
);
}
```
## API Reference
### Separator
`Separator` is equivalent to the `Root` import from `@kobalte/core/separator` (and deprecated `Separator.Root`).
| Prop | Description |
| --- | --- |
| orientation | `'horizontal' | 'vertical'`<br> The orientation of the separator. |
| Data attribute | Description |
| --- | --- |
| data-orientation='horizontal' | Present when the separator has horizontal orientation. |
| data-orientation='vertical' | Present when the separator has vertical orientation. |
## Rendered elements
| Component | Default rendered element |
| --- | --- |
| `Separator` | `hr` |
Previous[←Select](https://kobalte.dev/docs/core/components/select)Next[Skeleton→](https://kobalte.dev/docs/core/components/skeleton)

View File

@ -0,0 +1,244 @@
# Skeleton
Visually indicates content loading
## Import
```
Copyts
import { Skeleton } from "@kobalte/core/skeleton";
// or
import { Root } from "@kobalte/core/skeleton";
// or (deprecated)
import { Skeleton } from "@kobalte/core";
```
```
Copyts
import { Skeleton } from "@kobalte/core/skeleton";
// or
import { Root } from "@kobalte/core/skeleton";
// or (deprecated)
import { Skeleton } from "@kobalte/core";
```
## Features
- Support for custom width and height.
- Support for circle skeleton.
- Can toggle visiblity and animation properties.
## Anatomy
The skeleton consists of:
- **Skeleton:** The root container for a skeleton.
```
Copytsx
<Skeleton />
```
```
Copytsx
<Skeleton />
```
## Example
A UI toolkit for building accessible web apps and design systems with SolidJS.
index.tsxstyle.css
```
Copytsx
import { Skeleton } from "@kobalte/core/skeleton";
import "./style.css";
function App() {
return (
<Skeleton class="skeleton" radius={10}>
<p>
A UI toolkit for building accessible web apps and design systems with SolidJS.
</p>
</Skeleton>
);
}
```
```
Copytsx
import { Skeleton } from "@kobalte/core/skeleton";
import "./style.css";
function App() {
return (
<Skeleton class="skeleton" radius={10}>
<p>
A UI toolkit for building accessible web apps and design systems with SolidJS.
</p>
</Skeleton>
);
}
```
## Usage
### Multiple skeletons
![Nicole Steeves](https://pbs.twimg.com/profile_images/1509139491671445507/pzWYjlYN_400x400.jpg)
Kobalte
A UI toolkit for building accessible web apps and design systems with SolidJS.
index.tsxstyle.css
```
Copytsx
import { Skeleton } from "@kobalte/core/skeleton";
import { Image } from "@kobalte/core/image";
import "./style.css";
function App() {
return (
<div class="multiple-root">
<div class="multiple-profile">
<Skeleton class="skeleton" height={50} circle>
<Image class="multiple-avatar">
<Image.Img
class="image__img"
src="https://pbs.twimg.com/profile_images/1509139491671445507/pzWYjlYN_400x400.jpg"
alt="Nicole Steeves"
/>
</Image>
</Skeleton>
<Skeleton class="skeleton" height={20} radius={10}>
Kobalte
</Skeleton>
</div>
<Skeleton class="skeleton" radius={10}>
<p>
A UI toolkit for building accessible web apps and design systems with SolidJS.
</p>
</Skeleton>
</div>
);
}
```
```
Copytsx
import { Skeleton } from "@kobalte/core/skeleton";
import { Image } from "@kobalte/core/image";
import "./style.css";
function App() {
return (
<div class="multiple-root">
<div class="multiple-profile">
<Skeleton class="skeleton" height={50} circle>
<Image class="multiple-avatar">
<Image.Img
class="image__img"
src="https://pbs.twimg.com/profile_images/1509139491671445507/pzWYjlYN_400x400.jpg"
alt="Nicole Steeves"
/>
</Image>
</Skeleton>
<Skeleton class="skeleton" height={20} radius={10}>
Kobalte
</Skeleton>
</div>
<Skeleton class="skeleton" radius={10}>
<p>
A UI toolkit for building accessible web apps and design systems with SolidJS.
</p>
</Skeleton>
</div>
);
}
```
### Toggle example
Show content
A UI toolkit for building accessible web apps and design systems with SolidJS.
index.tsxstyle.css
```
Copytsx
import { Skeleton } from "@kobalte/core/skeleton";
import { ToggleButton } from "@kobalte/core/toggle-button";
import "./style.css";
function App() {
const [visible, setVisible] = createSignal(true);
return (
<div class="toggle-root">
<ToggleButton class="toggle-button" pressed={visible()} onChange={setVisible}>
Skeleton {visible() ? "Visible" : "Not Visible"}
</ToggleButton>
<Skeleton class="skeleton" visible={visible()}>
<p>
A UI toolkit for building accessible web apps and design systems with SolidJS.
</p>
</Skeleton>
</div>
);
}
```
```
Copytsx
import { Skeleton } from "@kobalte/core/skeleton";
import { ToggleButton } from "@kobalte/core/toggle-button";
import "./style.css";
function App() {
const [visible, setVisible] = createSignal(true);
return (
<div class="toggle-root">
<ToggleButton class="toggle-button" pressed={visible()} onChange={setVisible}>
Skeleton {visible() ? "Visible" : "Not Visible"}
</ToggleButton>
<Skeleton class="skeleton" visible={visible()}>
<p>
A UI toolkit for building accessible web apps and design systems with SolidJS.
</p>
</Skeleton>
</div>
);
}
```
## API Reference
### Skeleton
`Skeleton` is equivalent to the `Root` import from `@kobalte/core/skeleton` (and deprecated `Skeleton.Root`).
| Prop | Description |
| --- | --- |
| visible | `boolean`<br> The visible state of the Skeleton. Sets the `data-visible` data attribute. |
| animate | `boolean`<br> Whether the skeleton should animate. Sets the `data-animate` data attribute. |
| width | `number`<br> The width of the skeleton in px. Defaults to 100%. |
| height | `number`<br> The height of the skeleton in px. Defaults to auto. |
| radius | `number`<br> Roundness of the skeleton in px. Sets border-radius. |
| circle | `boolean`<br> Whether the skeleton should be a circle. Sets border-radius and width to the height. |
| children | `JSX.Element`<br> The children of the Skeleton. |
| Data attribute | Description |
| --- | --- |
| data-visible | Present when the Skeleton is visible. |
| data-animate | Present when the Skeleton can animate. |
## Rendered elements
| Component | Default rendered element |
| --- | --- |
| `Skeleton` | `div` |
Previous[←Separator](https://kobalte.dev/docs/core/components/separator)Next[Slider→](https://kobalte.dev/docs/core/components/slider)

View File

@ -0,0 +1,638 @@
# Slider
An input where the user selects a value from within a given range.
## Import
```
Copyts
import { Slider } from "@kobalte/core/slider";
// or
import { Root, Track, ... } from "@kobalte/core/slider";
// or (deprecated)
import { Slider } from "@kobalte/core";
```
```
Copyts
import { Slider } from "@kobalte/core/slider";
// or
import { Root, Track, ... } from "@kobalte/core/slider";
// or (deprecated)
import { Slider } from "@kobalte/core";
```
## Features
- Follow the [WAI ARIA Slider](https://www.w3.org/WAI/ARIA/apg/patterns/slider-multithumb/) design pattern.
- Can be controlled or uncontrolled.
- Support for multiple thumbs.
- Support a minimum step between thumbs.
- Support click or touch on track to change value.
- Support right or left direction.
- Support for custom value label.
## Anatomy
The slider consists of:
- **Slider:** The root container for the slider.
- **Slider.Track:** The component that visually represents the slider track.
- **Slider.Fill:** The component that visually represents the slider value.
- **Slider.Thumb:** The thumb that is used to visually indicate a value in the slider.
- **Slider.Input:** The native html input that is visually hidden in the slider thumb.
- **Slider.Label:** The label that gives the user information on the slider.
- **Slider.ValueLabel:** The accessible label text representing the current value in a human-readable format.
- **Slider.Description**: The description that gives the user more information on the slider.
- **Slider.ErrorMessage**: The error message that gives the user information about how to fix a validation error on the slider.
```
Copytsx
<Slider>
<Slider.Label />
<Slider.ValueLabel />
<Slider.Track>
<Slider.Fill />
<Slider.Thumb>
<Slider.Input />
</Slider.Thumb>
</Slider.Track>
<Slider.Description />
<Slider.ErrorMessage />
</Slider>
```
```
Copytsx
<Slider>
<Slider.Label />
<Slider.ValueLabel />
<Slider.Track>
<Slider.Fill />
<Slider.Thumb>
<Slider.Input />
</Slider.Thumb>
</Slider.Track>
<Slider.Description />
<Slider.ErrorMessage />
</Slider>
```
## Example
Label
0
index.tsxstyle.css
```
Copytsx
import { Slider } from "@kobalte/core/slider";
import "./style.css";
function App() {
return (
<Slider class="SliderRoot">
<div class="SliderLabel">
<Slider.Label>Label</Slider.Label>
<Slider.ValueLabel />
</div>
<Slider.Track class="SliderTrack">
<Slider.Fill class="SliderRange" />
<Slider.Thumb class="SliderThumb">
<Slider.Input />
</Slider.Thumb>
</Slider.Track>
</Slider>
);
}
```
```
Copytsx
import { Slider } from "@kobalte/core/slider";
import "./style.css";
function App() {
return (
<Slider class="SliderRoot">
<div class="SliderLabel">
<Slider.Label>Label</Slider.Label>
<Slider.ValueLabel />
</div>
<Slider.Track class="SliderTrack">
<Slider.Fill class="SliderRange" />
<Slider.Thumb class="SliderThumb">
<Slider.Input />
</Slider.Thumb>
</Slider.Track>
</Slider>
);
}
```
## Usage
### Multiple Thumbs
Label
0, 20
index.tsxstyle.css
```
Copytsx
import { Slider } from "@kobalte/core/slider";
import "./style.css";
function App() {
return (
<Slider class="SliderRoot" defaultValue={[0, 20]}>
<div class="SliderLabel">
<Slider.Label>Label</Slider.Label>
<Slider.ValueLabel />
</div>
<Slider.Track class="SliderTrack">
<Slider.Fill class="SliderRange" />
<Slider.Thumb class="SliderThumb">
<Slider.Input />
</Slider.Thumb>
<Slider.Thumb class="SliderThumb SliderThumb">
<Slider.Input />
</Slider.Thumb>
</Slider.Track>
</Slider>
);
}
```
```
Copytsx
import { Slider } from "@kobalte/core/slider";
import "./style.css";
function App() {
return (
<Slider class="SliderRoot" defaultValue={[0, 20]}>
<div class="SliderLabel">
<Slider.Label>Label</Slider.Label>
<Slider.ValueLabel />
</div>
<Slider.Track class="SliderTrack">
<Slider.Fill class="SliderRange" />
<Slider.Thumb class="SliderThumb">
<Slider.Input />
</Slider.Thumb>
<Slider.Thumb class="SliderThumb SliderThumb">
<Slider.Input />
</Slider.Thumb>
</Slider.Track>
</Slider>
);
}
```
### Modify step size
Step size 8
0
Step size 10
0
Step size 20
0
index.tsxstyle.css
```
Copytsx
import { Slider } from "@kobalte/core/slider";
import "./style.css";
function App() {
return (
<>
<Slider class="SliderRoot" step={8}>
<div class="SliderLabel">
<Slider.Label>Step size 8</Slider.Label>
<Slider.ValueLabel />
</div>
<Slider.Track class="SliderTrack">
<Slider.Fill class="SliderRange" />
<Slider.Thumb class="SliderThumb">
<Slider.Input />
</Slider.Thumb>
<Slider.Thumb class="SliderThumb">
<Slider.Input />
</Slider.Thumb>
</Slider.Track>
</Slider>
<Slider class="SliderRoot" step={10}>
<div class="SliderLabel">
<Slider.Label>Step size 10</Slider.Label>
<Slider.ValueLabel />
</div>
<Slider.Track class="SliderTrack">
<Slider.Fill class="SliderRange" />
<Slider.Thumb class="SliderThumb">
<Slider.Input />
</Slider.Thumb>
<Slider.Thumb class="SliderThumb">
<Slider.Input />
</Slider.Thumb>
</Slider.Track>
</Slider>
<Slider class="SliderRoot" step={20}>
<div class="SliderLabel">
<Slider.Label>Step size 20</Slider.Label>
<Slider.ValueLabel />
</div>
<Slider.Track class="SliderTrack">
<Slider.Fill class="SliderRange" />
<Slider.Thumb class="SliderThumb">
<Slider.Input />
</Slider.Thumb>
<Slider.Thumb class="SliderThumb">
<Slider.Input />
</Slider.Thumb>
</Slider.Track>
</Slider>
</>
);
}
```
```
Copytsx
import { Slider } from "@kobalte/core/slider";
import "./style.css";
function App() {
return (
<>
<Slider class="SliderRoot" step={8}>
<div class="SliderLabel">
<Slider.Label>Step size 8</Slider.Label>
<Slider.ValueLabel />
</div>
<Slider.Track class="SliderTrack">
<Slider.Fill class="SliderRange" />
<Slider.Thumb class="SliderThumb">
<Slider.Input />
</Slider.Thumb>
<Slider.Thumb class="SliderThumb">
<Slider.Input />
</Slider.Thumb>
</Slider.Track>
</Slider>
<Slider class="SliderRoot" step={10}>
<div class="SliderLabel">
<Slider.Label>Step size 10</Slider.Label>
<Slider.ValueLabel />
</div>
<Slider.Track class="SliderTrack">
<Slider.Fill class="SliderRange" />
<Slider.Thumb class="SliderThumb">
<Slider.Input />
</Slider.Thumb>
<Slider.Thumb class="SliderThumb">
<Slider.Input />
</Slider.Thumb>
</Slider.Track>
</Slider>
<Slider class="SliderRoot" step={20}>
<div class="SliderLabel">
<Slider.Label>Step size 20</Slider.Label>
<Slider.ValueLabel />
</div>
<Slider.Track class="SliderTrack">
<Slider.Fill class="SliderRange" />
<Slider.Thumb class="SliderThumb">
<Slider.Input />
</Slider.Thumb>
<Slider.Thumb class="SliderThumb">
<Slider.Input />
</Slider.Thumb>
</Slider.Track>
</Slider>
</>
);
}
```
### Steps between thumbs
Label
10, 20
index.tsxstyle.css
```
Copytsx
import { Slider } from "@kobalte/core/slider";
import "./style.css";
function App() {
return (
<Slider class="SliderRoot" defaultValue={[10, 20]} minStepsBetweenThumbs={10}>
<div class="SliderLabel">
<Slider.Label>Label</Slider.Label>
<Slider.ValueLabel />
</div>
<Slider.Track class="SliderTrack">
<Slider.Fill class="SliderRange" />
<Slider.Thumb class="SliderThumb">
<Slider.Input />
</Slider.Thumb>
<Slider.Thumb class="SliderThumb SliderThumb">
<Slider.Input />
</Slider.Thumb>
</Slider.Track>
</Slider>
);
}
```
```
Copytsx
import { Slider } from "@kobalte/core/slider";
import "./style.css";
function App() {
return (
<Slider class="SliderRoot" defaultValue={[10, 20]} minStepsBetweenThumbs={10}>
<div class="SliderLabel">
<Slider.Label>Label</Slider.Label>
<Slider.ValueLabel />
</div>
<Slider.Track class="SliderTrack">
<Slider.Fill class="SliderRange" />
<Slider.Thumb class="SliderThumb">
<Slider.Input />
</Slider.Thumb>
<Slider.Thumb class="SliderThumb SliderThumb">
<Slider.Input />
</Slider.Thumb>
</Slider.Track>
</Slider>
);
}
```
### Vertical Slider
Label
0
index.tsxstyle.css
```
Copytsx
import { Slider } from "@kobalte/core/slider";
import "./style.css";
function App() {
return (
<Slider class="SliderRoot" orientation="vertical">
<div class="SliderLabel">
<Slider.Label>Label</Slider.Label>
<Slider.ValueLabel />
</div>
<Slider.Track class="SliderTrack">
<Slider.Fill class="SliderRange" />
<Slider.Thumb class="SliderThumb">
<Slider.Input />
</Slider.Thumb>
</Slider.Track>
</Slider>
);
}
```
```
Copytsx
import { Slider } from "@kobalte/core/slider";
import "./style.css";
function App() {
return (
<Slider class="SliderRoot" orientation="vertical">
<div class="SliderLabel">
<Slider.Label>Label</Slider.Label>
<Slider.ValueLabel />
</div>
<Slider.Track class="SliderTrack">
<Slider.Fill class="SliderRange" />
<Slider.Thumb class="SliderThumb">
<Slider.Input />
</Slider.Thumb>
</Slider.Track>
</Slider>
);
}
```
### Custom Value Label
Money
$20 - $500
index.tsxstyle.css
```
Copytsx
import { Slider } from "@kobalte/core/slider";
import "./style.css";
function App() {
return (
<Slider class="SliderRoot"
minValue={10}
maxValue={2000}
defaultValue={[20, 500]}
getValueLabel={params => `$${params.values[0]} - $${params.values[1]}`}
>
<div class="SliderLabel">
<Slider.Label>Money</Slider.Label>
<Slider.ValueLabel />
</div>
<Slider.Track class="SliderTrack">
<Slider.Fill class="SliderRange" />
<Slider.Thumb class="SliderThumb">
<Slider.Input />
</Slider.Thumb>
<Slider.Thumb class="SliderThumb SliderThumb">
<Slider.Input />
</Slider.Thumb>
</Slider.Track>
</Slider>
);
}
```
```
Copytsx
import { Slider } from "@kobalte/core/slider";
import "./style.css";
function App() {
return (
<Slider class="SliderRoot"
minValue={10}
maxValue={2000}
defaultValue={[20, 500]}
getValueLabel={params => `$${params.values[0]} - $${params.values[1]}`}
>
<div class="SliderLabel">
<Slider.Label>Money</Slider.Label>
<Slider.ValueLabel />
</div>
<Slider.Track class="SliderTrack">
<Slider.Fill class="SliderRange" />
<Slider.Thumb class="SliderThumb">
<Slider.Input />
</Slider.Thumb>
<Slider.Thumb class="SliderThumb SliderThumb">
<Slider.Input />
</Slider.Thumb>
</Slider.Track>
</Slider>
);
}
```
### Controlled Value
Label
40
index.tsxstyle.css
```
Copytsx
import { createSignal } from 'solid-js'
import { Slider } from "@kobalte/core/slider";
import "./style.css";
function App() {
const [values, setValues] = createSignal<number[]>([40])
return (
<Slider class="SliderRoot" value={values()} onChange={setValues}>
<div class="SliderLabel">
<Slider.Label>Label</Slider.Label>
<Slider.ValueLabel />
</div>
<Slider.Track class="SliderTrack">
<Slider.Fill class="SliderRange" />
<Slider.Thumb class="SliderThumb">
<Slider.Input />
</Slider.Thumb>
</Slider.Track>
</Slider>
);
}
```
```
Copytsx
import { createSignal } from 'solid-js'
import { Slider } from "@kobalte/core/slider";
import "./style.css";
function App() {
const [values, setValues] = createSignal<number[]>([40])
return (
<Slider class="SliderRoot" value={values()} onChange={setValues}>
<div class="SliderLabel">
<Slider.Label>Label</Slider.Label>
<Slider.ValueLabel />
</div>
<Slider.Track class="SliderTrack">
<Slider.Fill class="SliderRange" />
<Slider.Thumb class="SliderThumb">
<Slider.Input />
</Slider.Thumb>
</Slider.Track>
</Slider>
);
}
```
## API Reference
### Slider
`Slider` is equivalent to the `Root` import from `@kobalte/core/slider` (and deprecated `Slider.Root`).
| Prop | Description |
| --- | --- |
| value | `number[]`<br> The controlled values of the slider. Must be used in conjunction with `onChange`. |
| defaultValue | `number[]`<br> The value of the slider when initially rendered. Use when you do not need to control the state of the slider. |
| onChange | `(value: number[]) => void`<br> Event handler called when the value changes. |
| onChangeEnd | `(value: number[]) => void`<br> Event handler called when the value changes at the end of an interaction. |
| inverted | `boolean`<br> Whether the slider is visually inverted. Defaults to false. |
| minValue | `number`<br> The minimum slider value. Defaults to 0 |
| maxValue | `number`<br> The maximum slider value. Defaults to 100 |
| step | `number`<br> The stepping interval. Defaults to 1 |
| minStepsBetweenThumbs | `number`<br> The minimum permitted steps between thumbs. Defaults to 0 |
| getValueLabel | `(params: GetValueLabelParams) => string`<br> A function to get the accessible label text representing the current value in a human-readable format. If not provided, the value label will be read as a percentage of the max value. |
| orientation | `'horizontal' | 'vertical'`<br> The orientation of the slider. |
| name | `string`<br> The name of the slider, used when submitting an HTML form. |
| validationState | `'valid' | 'invalid'`<br> Whether the slider should display its "valid" or "invalid" visual styling. |
| required | `boolean`<br> Whether the user must check a radio group item before the owning form can be submitted. |
| disabled | `boolean`<br> Whether the radio group is disabled. |
| readOnly | `boolean`<br> Whether the radio group items can be selected but not changed by the user. |
| Data attribute | Description |
| --- | --- |
| data-orientation='horizontal' | Present when the slider has horizontal orientation. |
| data-orientation='vertical' | Present when the slider has vertical orientation. |
| data-valid | Present when the slider is valid according to the validation rules. |
| data-invalid | Present when the slider is invalid according to the validation rules. |
| data-required | Present when the user must slider an item before the owning form can be submitted. |
| data-disabled | Present when the slider is disabled. |
| data-readonly | Present when the slider is read only. |
`Slider.ValueLabel`, `Slider.Fill`, `Slider.Input`, `Slider.Thumb` and `Slider.Track` share the same data-attributes.
## Rendered elements
| Component | Default rendered element |
| --- | --- |
| `Slider` | `div` |
| `Slider.Track` | `div` |
| `Slider.Fill` | `div` |
| `Slider.Thumb` | `span` |
| `Slider.Input` | `input` |
| `Slider.ValueLabel` | `div` |
| `Slider.Description` | `div` |
| `Slider.ErrorMessage` | `div` |
## Accessibility
### Keyboard Interactions
| Key | Description |
| --- | --- |
| `PageUp` | Increases the value of the focused thumb by a larger `step`. |
| `PageDown` | Decreases the value of the focused thumb by a larger `step`. |
| `ArrowDown` | Decreases the value of the focused thumb by the `step` amount. |
| `ArrowUp` | Increases the value of the focused thumb by the `step` amount. |
| `ArrowRight` | Increments/decrements by the `step` value depending on `orientation`. |
| `ArrowLeft` | Increments/decrements by the `step` value depending on `orientation`. |
| `Home` | Sets the value of the first thumb to the minimum value. |
| `End` | Sets the value of the last thumb to the maximum value. |
Previous[←Skeleton](https://kobalte.dev/docs/core/components/skeleton)Next[Switch→](https://kobalte.dev/docs/core/components/switch)

View File

@ -0,0 +1,373 @@
# Switch
A control that allows users to choose one of two values: on or off.
## Import
```
Copyts
import { Switch } from "@kobalte/core/switch";
// or
import { Root, Input, ... } from "@kobalte/core/switch";
// or (deprecated)
import { Switch } from "@kobalte/core";
```
```
Copyts
import { Switch } from "@kobalte/core/switch";
// or
import { Root, Input, ... } from "@kobalte/core/switch";
// or (deprecated)
import { Switch } from "@kobalte/core";
```
## Features
- Follow the [WAI ARIA Switch](https://www.w3.org/WAI/ARIA/apg/patterns/switch/) design pattern.
- Built with a native HTML `<input>` element, which is visually hidden to allow custom styling.
- Syncs with form reset events.
- Labeling support for assistive technology.
- Support for description and error message help text linked to the input via ARIA.
- Can be controlled or uncontrolled.
## Anatomy
The switch consists of:
- **Switch:** The root container for a switch.
- **Switch.Input:** The native html input that is visually hidden in the switch.
- **Switch.Control:** The element that visually represents a switch.
- **Switch.Thumb:** The thumb that is used to visually indicate whether the switch is on or off.
- **Switch.Label:** The label that gives the user information on the switch.
- **Switch.Description**: The description that gives the user more information on the switch.
- **Switch.ErrorMessage**: The error message that gives the user information about how to fix a validation error on the switch.
```
Copytsx
<Switch>
<Switch.Label />
<Switch.Description />
<Switch.ErrorMessage />
<Switch.Input />
<Switch.Control>
<Switch.Thumb />
</Switch.Control>
</Switch>
```
```
Copytsx
<Switch>
<Switch.Label />
<Switch.Description />
<Switch.ErrorMessage />
<Switch.Input />
<Switch.Control>
<Switch.Thumb />
</Switch.Control>
</Switch>
```
## Example
Airplane mode
index.tsxstyle.css
```
Copytsx
import { Switch } from "@kobalte/core/switch";
import "./style.css";
function App() {
return (
<Switch class="switch">
<Switch.Label class="switch__label">Airplane mode</Switch.Label>
<Switch.Input class="switch__input" />
<Switch.Control class="switch__control">
<Switch.Thumb class="switch__thumb" />
</Switch.Control>
</Switch>
);
}
```
```
Copytsx
import { Switch } from "@kobalte/core/switch";
import "./style.css";
function App() {
return (
<Switch class="switch">
<Switch.Label class="switch__label">Airplane mode</Switch.Label>
<Switch.Input class="switch__input" />
<Switch.Control class="switch__control">
<Switch.Thumb class="switch__thumb" />
</Switch.Control>
</Switch>
);
}
```
## Usage
### Default checked
An initial, uncontrolled value can be provided using the `defaultChecked` prop.
Airplane mode
```
Copytsx
<Switch defaultChecked>...</Switch>
```
```
Copytsx
<Switch defaultChecked>...</Switch>
```
### Controlled checked
The `checked` prop can be used to make the checked state controlled. The `onChange` event is fired when the user toggle the switch, and receives the new value.
Airplane mode
Airplane mode is inactive.
```
Copytsx
import { createSignal } from "solid-js";
export function ControlledExample() {
const [checked, setChecked] = createSignal(false);
return (
<>
<Switch checked={checked()} onChange={setChecked}>
...
</Switch>
<p>Airplane mode is {checked() ? "active" : "inactive"}.</p>
</>
);
}
```
```
Copytsx
import { createSignal } from "solid-js";
export function ControlledExample() {
const [checked, setChecked] = createSignal(false);
return (
<>
<Switch checked={checked()} onChange={setChecked}>
...
</Switch>
<p>Airplane mode is {checked() ? "active" : "inactive"}.</p>
</>
);
}
```
### Description
The `Switch.Description` component can be used to associate additional help text with a switch.
Airplane mode
Disable all network connections.
```
Copytsx
<Switch>
<Switch.Label>Airplane mode</Switch.Label>
<Switch.Description>Disable all network connections.</Switch.Description>
<Switch.Input />
<Switch.Control>
<Switch.Thumb />
</Switch.Control>
</Switch>
```
```
Copytsx
<Switch>
<Switch.Label>Airplane mode</Switch.Label>
<Switch.Description>Disable all network connections.</Switch.Description>
<Switch.Input />
<Switch.Control>
<Switch.Thumb />
</Switch.Control>
</Switch>
```
### Error message
The `Switch.ErrorMessage` component can be used to help the user fix a validation error. It should be combined with the `validationState` prop to semantically mark the switch as invalid for assistive technologies.
By default, it will render only when the `validationState` prop is set to `invalid`, use the `forceMount` prop to always render the error message (ex: for usage with animation libraries).
Airplane mode
You must enable airplane mode.
```
Copytsx
import { createSignal } from "solid-js";
function ErrorMessageExample() {
const [checked, setChecked] = createSignal(false);
return (
<Switch
checked={checked()}
onChange={setChecked}
validationState={!checked() ? "invalid" : "valid"}
>
<Switch.Label>Airplane mode</Switch.Label>
<Switch.ErrorMessage>You must enable airplane mode.</Switch.ErrorMessage>
<Switch.Input />
<Switch.Control>
<Switch.Thumb />
</Switch.Control>
</Switch>
);
}
```
```
Copytsx
import { createSignal } from "solid-js";
function ErrorMessageExample() {
const [checked, setChecked] = createSignal(false);
return (
<Switch
checked={checked()}
onChange={setChecked}
validationState={!checked() ? "invalid" : "valid"}
>
<Switch.Label>Airplane mode</Switch.Label>
<Switch.ErrorMessage>You must enable airplane mode.</Switch.ErrorMessage>
<Switch.Input />
<Switch.Control>
<Switch.Thumb />
</Switch.Control>
</Switch>
);
}
```
### HTML forms
The `name` and `value` props can be used for integration with HTML forms.
Airplane mode
ResetSubmit
```
Copytsx
function HTMLFormExample() {
const onSubmit = (e: SubmitEvent) => {
// handle form submission.
};
return (
<form onSubmit={onSubmit}>
<Switch name="airplane-mode" value="on">
...
</Switch>
<div>
<button type="reset">Reset</button>
<button type="submit">Submit</button>
</div>
</form>
);
}
```
```
Copytsx
function HTMLFormExample() {
const onSubmit = (e: SubmitEvent) => {
// handle form submission.
};
return (
<form onSubmit={onSubmit}>
<Switch name="airplane-mode" value="on">
...
</Switch>
<div>
<button type="reset">Reset</button>
<button type="submit">Submit</button>
</div>
</form>
);
}
```
## API Reference
### Switch
`Switch` is equivalent to the `Root` import from `@kobalte/core/switch` (and deprecated `Switch.Root`).
| Prop | Description |
| --- | --- |
| checked | `boolean`<br> The controlled checked state of the switch. |
| defaultChecked | `boolean`<br> The default checked state when initially rendered. Useful when you do not need to control the checked state. |
| onChange | `(checked: boolean) => void`<br> Event handler called when the checked state of the switch changes. |
| name | `string`<br> The name of the switch, used when submitting an HTML form. See [MDN](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#htmlattrdefname). |
| value | `string`<br> The value of the switch, used when submitting an HTML form. See [MDN](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#htmlattrdefvalue). |
| validationState | `'valid' | 'invalid'`<br> Whether the switch should display its "valid" or "invalid" visual styling. |
| required | `boolean`<br> Whether the user must check the switch before the owning form can be submitted. |
| disabled | `boolean`<br> Whether the switch is disabled. |
| readOnly | `boolean`<br> Whether the switch can be checked but not changed by the user. |
| Render Prop | Description |
| --- | --- |
| checked | `Accessor<boolean>`<br> Whether the switch is checked or not. |
| Data attribute | Description |
| --- | --- |
| data-valid | Present when the switch is valid according to the validation rules. |
| data-invalid | Present when the switch is invalid according to the validation rules. |
| data-required | Present when the switch is required. |
| data-disabled | Present when the switch is disabled. |
| data-readonly | Present when the switch is read only. |
| data-checked | Present when the switch is checked. |
`Switch.Input`, `Switch.Control`, `Switch.Thumb`, `Switch.Label`, `Switch.Description` and `Switch.ErrorMessage` shares the same data-attributes.
### Switch.ErrorMessage
| Prop | Description |
| --- | --- |
| forceMount | `boolean`<br> Used to force mounting when more control is needed. Useful when controlling animation with SolidJS animation libraries. |
## Rendered elements
| Component | Default rendered element |
| --- | --- |
| `Switch` | `div` |
| `Switch.Input` | `input` |
| `Switch.Control` | `div` |
| `Switch.Indicator` | `div` |
| `Switch.Label` | `label` |
| `Switch.Description` | `div` |
| `Switch.ErrorMessage` | `div` |
## Accessibility
### Keyboard Interactions
| Key | Description |
| --- | --- |
| `Space` | Toggles the switch on and off. |
Previous[←Slider](https://kobalte.dev/docs/core/components/slider)Next[Tabs→](https://kobalte.dev/docs/core/components/tabs)

View File

@ -0,0 +1,631 @@
# Tabs
A set of layered sections of content, known as tab panels, that display one panel of content at a time.
## Import
```
Copyts
import { Tabs } from "@kobalte/core/tabs";
// or
import { Root, List, ... } from "@kobalte/core/tabs";
// or (deprecated)
import { Tabs } from "@kobalte/core";
```
```
Copyts
import { Tabs } from "@kobalte/core/tabs";
// or
import { Root, List, ... } from "@kobalte/core/tabs";
// or (deprecated)
import { Tabs } from "@kobalte/core";
```
## Features
- Follow the [WAI ARIA Tabs](https://www.w3.org/WAI/ARIA/apg/patterns/tabs/) design pattern, semantically linking tabs and their associated tab panels.
- Support for LTR and RTL keyboard navigation.
- Support for disabled tabs.
- Supports horizontal/vertical orientation.
- Supports automatic/manual activation.
- Focus management for tab panels without any focusable children.
- Can be controlled or uncontrolled.
## Anatomy
The tabs consist of:
- **Tabs:** The root container for tabs and tab contents.
- **Tabs.List:** Contains the tabs that are aligned along the edge of the active tab content.
- **Tabs.Trigger:** The button that activates its associated tab content.
- **Tabs.Indicator:** The visual indicator displayed at the bottom of the tab list to indicate the selected tab.
- **Tabs.Content:** Contains the content associated with a tab trigger.
```
Copytsx
<Tabs>
<Tabs.List>
<Tabs.Trigger />
<Tabs.Indicator />
</Tabs.List>
<Tabs.Content />
</Tabs>
```
```
Copytsx
<Tabs>
<Tabs.List>
<Tabs.Trigger />
<Tabs.Indicator />
</Tabs.List>
<Tabs.Content />
</Tabs>
```
## Example
ProfileDashboardSettingsContact
Profile details
index.tsxstyle.css
```
Copytsx
import { Tabs } from "@kobalte/core/tabs";
import "./style.css";
function App() {
return (
<Tabs aria-label="Main navigation" class="tabs">
<Tabs.List class="tabs__list">
<Tabs.Trigger class="tabs__trigger" value="profile">Profile</Tabs.Trigger>
<Tabs.Trigger class="tabs__trigger" value="dashboard">Dashboard</Tabs.Trigger>
<Tabs.Trigger class="tabs__trigger" value="settings">Settings</Tabs.Trigger>
<Tabs.Trigger class="tabs__trigger" value="contact">Contact</Tabs.Trigger>
<Tabs.Indicator class="tabs__indicator" />
</Tabs.List>
<Tabs.Content class="tabs__content" value="profile">Profile details</Tabs.Content>
<Tabs.Content class="tabs__content" value="dashboard">Dashboard details</Tabs.Content>
<Tabs.Content class="tabs__content" value="settings">Settings details</Tabs.Content>
<Tabs.Content class="tabs__content" value="contact">Contact details</Tabs.Content>
</Tabs>
);
}
```
```
Copytsx
import { Tabs } from "@kobalte/core/tabs";
import "./style.css";
function App() {
return (
<Tabs aria-label="Main navigation" class="tabs">
<Tabs.List class="tabs__list">
<Tabs.Trigger class="tabs__trigger" value="profile">Profile</Tabs.Trigger>
<Tabs.Trigger class="tabs__trigger" value="dashboard">Dashboard</Tabs.Trigger>
<Tabs.Trigger class="tabs__trigger" value="settings">Settings</Tabs.Trigger>
<Tabs.Trigger class="tabs__trigger" value="contact">Contact</Tabs.Trigger>
<Tabs.Indicator class="tabs__indicator" />
</Tabs.List>
<Tabs.Content class="tabs__content" value="profile">Profile details</Tabs.Content>
<Tabs.Content class="tabs__content" value="dashboard">Dashboard details</Tabs.Content>
<Tabs.Content class="tabs__content" value="settings">Settings details</Tabs.Content>
<Tabs.Content class="tabs__content" value="contact">Contact details</Tabs.Content>
</Tabs>
);
}
```
## Usage
### Default value
A default selected tab can be provided using the `defaultValue` prop, which should correspond to the `value` prop provided to each tab.
ProfileDashboardSettingsContact
Dashboard details
```
Copytsx
<Tabs defaultValue="dashboard">
<Tabs.List>
<Tabs.Trigger value="profile">Profile</Tabs.Trigger>
<Tabs.Trigger value="dashboard">Dashboard</Tabs.Trigger>
<Tabs.Trigger value="settings">Settings</Tabs.Trigger>
<Tabs.Trigger value="contact">Contact</Tabs.Trigger>
<Tabs.Indicator />
</Tabs.List>
<Tabs.Content value="profile">Profile details</Tabs.Content>
<Tabs.Content value="dashboard">Dashboard details</Tabs.Content>
<Tabs.Content value="settings">Settings details</Tabs.Content>
<Tabs.Content value="contact">Contact details</Tabs.Content>
</Tabs>
```
```
Copytsx
<Tabs defaultValue="dashboard">
<Tabs.List>
<Tabs.Trigger value="profile">Profile</Tabs.Trigger>
<Tabs.Trigger value="dashboard">Dashboard</Tabs.Trigger>
<Tabs.Trigger value="settings">Settings</Tabs.Trigger>
<Tabs.Trigger value="contact">Contact</Tabs.Trigger>
<Tabs.Indicator />
</Tabs.List>
<Tabs.Content value="profile">Profile details</Tabs.Content>
<Tabs.Content value="dashboard">Dashboard details</Tabs.Content>
<Tabs.Content value="settings">Settings details</Tabs.Content>
<Tabs.Content value="contact">Contact details</Tabs.Content>
</Tabs>
```
### Controlled value
Selected tab can be controlled using the `value` prop, paired with the `onChange` event. The `value` prop from the selected tab will be passed into the callback when the tab is selected, allowing you to update state accordingly.
ProfileDashboardSettingsContact
Settings details
Selected tab: settings
```
Copytsx
import { createSignal } from "solid-js";
function ControlledExample() {
const [selectedTab, setSelectedTab] = createSignal("settings");
return (
<>
<Tabs value={selectedTab()} onChange={setSelectedTab}>
<Tabs.List>
<Tabs.Trigger value="profile">Profile</Tabs.Trigger>
<Tabs.Trigger value="dashboard">Dashboard</Tabs.Trigger>
<Tabs.Trigger value="settings">Settings</Tabs.Trigger>
<Tabs.Trigger value="contact">Contact</Tabs.Trigger>
<Tabs.Indicator />
</Tabs.List>
<Tabs.Content value="profile">Profile details</Tabs.Content>
<Tabs.Content value="dashboard">Dashboard details</Tabs.Content>
<Tabs.Content value="settings">Settings details</Tabs.Content>
<Tabs.Content value="contact">Contact details</Tabs.Content>
</Tabs>
<p>Selected tab: {selectedTab()}</p>
</>
);
}
```
```
Copytsx
import { createSignal } from "solid-js";
function ControlledExample() {
const [selectedTab, setSelectedTab] = createSignal("settings");
return (
<>
<Tabs value={selectedTab()} onChange={setSelectedTab}>
<Tabs.List>
<Tabs.Trigger value="profile">Profile</Tabs.Trigger>
<Tabs.Trigger value="dashboard">Dashboard</Tabs.Trigger>
<Tabs.Trigger value="settings">Settings</Tabs.Trigger>
<Tabs.Trigger value="contact">Contact</Tabs.Trigger>
<Tabs.Indicator />
</Tabs.List>
<Tabs.Content value="profile">Profile details</Tabs.Content>
<Tabs.Content value="dashboard">Dashboard details</Tabs.Content>
<Tabs.Content value="settings">Settings details</Tabs.Content>
<Tabs.Content value="contact">Contact details</Tabs.Content>
</Tabs>
<p>Selected tab: {selectedTab()}</p>
</>
);
}
```
### Focusable content
When the tab content doesn't contain any focusable content, the entire content is given a `tabIndex=0` so that the content can be navigated to with the keyboard.
When the tab content contains focusable content, such as an `<input>`, then the `tabIndex` is omitted because the content itself can receive focus.
This example uses the same Tabs components from above. Try navigating from the tabs to each content using the keyboard.
ProfileDashboardSettingsContact
```
Copytsx
function FocusableContentExample() {
return (
<Tabs>
<Tabs.List>
<Tabs.Trigger value="profile">Profile</Tabs.Trigger>
<Tabs.Trigger value="dashboard">Dashboard</Tabs.Trigger>
<Tabs.Trigger value="settings">Settings</Tabs.Trigger>
<Tabs.Trigger value="contact">Contact</Tabs.Trigger>
<Tabs.Indicator />
</Tabs.List>
<Tabs.Content value="profile">
<input placeholder="Change password" />
</Tabs.Content>
<Tabs.Content value="dashboard">Dashboard details</Tabs.Content>
<Tabs.Content value="settings">Settings details</Tabs.Content>
<Tabs.Content value="contact">Contact details</Tabs.Content>
</Tabs>
);
}
```
```
Copytsx
function FocusableContentExample() {
return (
<Tabs>
<Tabs.List>
<Tabs.Trigger value="profile">Profile</Tabs.Trigger>
<Tabs.Trigger value="dashboard">Dashboard</Tabs.Trigger>
<Tabs.Trigger value="settings">Settings</Tabs.Trigger>
<Tabs.Trigger value="contact">Contact</Tabs.Trigger>
<Tabs.Indicator />
</Tabs.List>
<Tabs.Content value="profile">
<input placeholder="Change password" />
</Tabs.Content>
<Tabs.Content value="dashboard">Dashboard details</Tabs.Content>
<Tabs.Content value="settings">Settings details</Tabs.Content>
<Tabs.Content value="contact">Contact details</Tabs.Content>
</Tabs>
);
}
```
### Dynamic tabs
Tabs can be added/removed dynamically by using a signal and SolidJS `For`.
Add tabRemove tab
Tab 1Tab 2Tab 3
Tab body 1
```
Copytsx
import { createSignal } from "solid-js";
function DynamicContentExample() {
const [tabs, setTabs] = createSignal([\
{ id: "1", title: "Tab 1", content: "Tab body 1" },\
{ id: "2", title: "Tab 2", content: "Tab body 2" },\
{ id: "3", title: "Tab 3", content: "Tab body 3" },\
]);
const addTab = () => {
setTabs(prev => [\
...prev,\
{\
id: String(prev.length + 1),\
title: `Tab ${prev.length + 1}`,\
content: `Tab Body ${prev.length + 1}`,\
},\
]);
};
const removeTab = () => {
if (tabs().length > 1) {
setTabs(prev => prev.slice(0, -1));
}
};
return (
<>
<button onClick={addTab}>Add tab</button>
<button onClick={removeTab}>Remove tab</button>
<Tabs>
<Tabs.List>
<For each={tabs()}>{tab => <Tabs.Trigger value={tab.id}>{tab.title}</Tabs.Trigger>}</For>
<Tabs.Indicator />
</Tabs.List>
<For each={tabs()}>{tab => <Tabs.Content value={tab.id}>{tab.content}</Tabs.Content>}</For>
</Tabs>
</>
);
}
```
```
Copytsx
import { createSignal } from "solid-js";
function DynamicContentExample() {
const [tabs, setTabs] = createSignal([\
{ id: "1", title: "Tab 1", content: "Tab body 1" },\
{ id: "2", title: "Tab 2", content: "Tab body 2" },\
{ id: "3", title: "Tab 3", content: "Tab body 3" },\
]);
const addTab = () => {
setTabs(prev => [\
...prev,\
{\
id: String(prev.length + 1),\
title: `Tab ${prev.length + 1}`,\
content: `Tab Body ${prev.length + 1}`,\
},\
]);
};
const removeTab = () => {
if (tabs().length > 1) {
setTabs(prev => prev.slice(0, -1));
}
};
return (
<>
<button onClick={addTab}>Add tab</button>
<button onClick={removeTab}>Remove tab</button>
<Tabs>
<Tabs.List>
<For each={tabs()}>{tab => <Tabs.Trigger value={tab.id}>{tab.title}</Tabs.Trigger>}</For>
<Tabs.Indicator />
</Tabs.List>
<For each={tabs()}>{tab => <Tabs.Content value={tab.id}>{tab.content}</Tabs.Content>}</For>
</Tabs>
</>
);
}
```
### Activation mode
By default, pressing the arrow keys while focus is on a Tab will switch selection to the adjacent Tab in that direction, updating the content displayed accordingly.
If you would like to prevent selection change from happening automatically you can set the `activationMode` prop to `manual`. This will prevent tab selection from changing on arrow key press, requiring a subsequent `Enter` or `Space` key press to confirm tab selection.
ProfileDashboardSettingsContact
Profile details
```
Copytsx
<Tabs activationMode="manual">
<Tabs.List>
<Tabs.Trigger value="profile">Profile</Tabs.Trigger>
<Tabs.Trigger value="dashboard">Dashboard</Tabs.Trigger>
<Tabs.Trigger value="settings">Settings</Tabs.Trigger>
<Tabs.Trigger value="contact">Contact</Tabs.Trigger>
<Tabs.Indicator />
</Tabs.List>
<Tabs.Content value="profile">Profile details</Tabs.Content>
<Tabs.Content value="dashboard">Dashboard details</Tabs.Content>
<Tabs.Content value="settings">Settings details</Tabs.Content>
<Tabs.Content value="contact">Contact details</Tabs.Content>
</Tabs>
```
```
Copytsx
<Tabs activationMode="manual">
<Tabs.List>
<Tabs.Trigger value="profile">Profile</Tabs.Trigger>
<Tabs.Trigger value="dashboard">Dashboard</Tabs.Trigger>
<Tabs.Trigger value="settings">Settings</Tabs.Trigger>
<Tabs.Trigger value="contact">Contact</Tabs.Trigger>
<Tabs.Indicator />
</Tabs.List>
<Tabs.Content value="profile">Profile details</Tabs.Content>
<Tabs.Content value="dashboard">Dashboard details</Tabs.Content>
<Tabs.Content value="settings">Settings details</Tabs.Content>
<Tabs.Content value="contact">Contact details</Tabs.Content>
</Tabs>
```
### Orientation
By default, tabs are horizontally oriented. The `orientation` prop can be set to `vertical` to change this. This affects keyboard navigation. You are responsible for styling your tabs accordingly.
ProfileDashboardSettingsContact
Profile details
```
Copytsx
<Tabs orientation="vertical">
<Tabs.List>
<Tabs.Trigger value="profile">Profile</Tabs.Trigger>
<Tabs.Trigger value="dashboard">Dashboard</Tabs.Trigger>
<Tabs.Trigger value="settings">Settings</Tabs.Trigger>
<Tabs.Trigger value="contact">Contact</Tabs.Trigger>
<Tabs.Indicator />
</Tabs.List>
<Tabs.Content value="profile">Profile details</Tabs.Content>
<Tabs.Content value="dashboard">Dashboard details</Tabs.Content>
<Tabs.Content value="settings">Settings details</Tabs.Content>
<Tabs.Content value="contact">Contact details</Tabs.Content>
</Tabs>
```
```
Copytsx
<Tabs orientation="vertical">
<Tabs.List>
<Tabs.Trigger value="profile">Profile</Tabs.Trigger>
<Tabs.Trigger value="dashboard">Dashboard</Tabs.Trigger>
<Tabs.Trigger value="settings">Settings</Tabs.Trigger>
<Tabs.Trigger value="contact">Contact</Tabs.Trigger>
<Tabs.Indicator />
</Tabs.List>
<Tabs.Content value="profile">Profile details</Tabs.Content>
<Tabs.Content value="dashboard">Dashboard details</Tabs.Content>
<Tabs.Content value="settings">Settings details</Tabs.Content>
<Tabs.Content value="contact">Contact details</Tabs.Content>
</Tabs>
```
### Disabled
All tabs can be disabled using the `disabled` prop.
ProfileDashboardSettingsContact
Profile details
```
Copytsx
<Tabs disabled>
<Tabs.List>
<Tabs.Trigger value="profile">Profile</Tabs.Trigger>
<Tabs.Trigger value="dashboard">Dashboard</Tabs.Trigger>
<Tabs.Trigger value="settings">Settings</Tabs.Trigger>
<Tabs.Trigger value="contact">Contact</Tabs.Trigger>
<Tabs.Indicator />
</Tabs.List>
<Tabs.Content value="profile">Profile details</Tabs.Content>
<Tabs.Content value="dashboard">Dashboard details</Tabs.Content>
<Tabs.Content value="settings">Settings details</Tabs.Content>
<Tabs.Content value="contact">Contact details</Tabs.Content>
</Tabs>
```
```
Copytsx
<Tabs disabled>
<Tabs.List>
<Tabs.Trigger value="profile">Profile</Tabs.Trigger>
<Tabs.Trigger value="dashboard">Dashboard</Tabs.Trigger>
<Tabs.Trigger value="settings">Settings</Tabs.Trigger>
<Tabs.Trigger value="contact">Contact</Tabs.Trigger>
<Tabs.Indicator />
</Tabs.List>
<Tabs.Content value="profile">Profile details</Tabs.Content>
<Tabs.Content value="dashboard">Dashboard details</Tabs.Content>
<Tabs.Content value="settings">Settings details</Tabs.Content>
<Tabs.Content value="contact">Contact details</Tabs.Content>
</Tabs>
```
Individual tab can be disabled using the `disabled` prop on the tab itself.
ProfileDashboardSettingsContact
Profile details
```
Copytsx
<Tabs>
<Tabs.List>
<Tabs.Trigger value="profile">Profile</Tabs.Trigger>
<Tabs.Trigger value="dashboard">Dashboard</Tabs.Trigger>
<Tabs.Trigger value="settings" disabled>
Settings
</Tabs.Trigger>
<Tabs.Trigger value="contact">Contact</Tabs.Trigger>
<Tabs.Indicator />
</Tabs.List>
<Tabs.Content value="profile">Profile details</Tabs.Content>
<Tabs.Content value="dashboard">Dashboard details</Tabs.Content>
<Tabs.Content value="settings">Settings details</Tabs.Content>
<Tabs.Content value="contact">Contact details</Tabs.Content>
</Tabs>
```
```
Copytsx
<Tabs>
<Tabs.List>
<Tabs.Trigger value="profile">Profile</Tabs.Trigger>
<Tabs.Trigger value="dashboard">Dashboard</Tabs.Trigger>
<Tabs.Trigger value="settings" disabled>
Settings
</Tabs.Trigger>
<Tabs.Trigger value="contact">Contact</Tabs.Trigger>
<Tabs.Indicator />
</Tabs.List>
<Tabs.Content value="profile">Profile details</Tabs.Content>
<Tabs.Content value="dashboard">Dashboard details</Tabs.Content>
<Tabs.Content value="settings">Settings details</Tabs.Content>
<Tabs.Content value="contact">Contact details</Tabs.Content>
</Tabs>
```
## API Reference
### Tabs
`Tabs` is equivalent to the `Root` import from `@kobalte/core/tabs` (and deprecated `Tabs.Root`).
| Prop | Description |
| --- | --- |
| value | `string`<br> The controlled value of the tab to activate. |
| defaultValue | `string`<br> The value of the tab that should be active when initially rendered. Useful when you do not need to control the state. |
| onChange | `(value: string) => void`<br> Event handler called when the value changes. |
| orientation | `'horizontal' | 'vertical'`<br> The orientation of the tabs. |
| activationMode | `'automatic' | 'manual'`<br> Whether tabs are activated automatically on focus or manually. |
| disabled | `boolean`<br> Whether the tabs are disabled. |
| Data attribute | Description |
| --- | --- |
| data-orientation='horizontal' | Present when the separator has horizontal orientation. |
| data-orientation='vertical' | Present when the separator has vertical orientation. |
`Tabs.List`, `Tabs.Trigger`, `Tabs.Indicator` and `Tabs.Content` share the same data-attributes.
### Tabs.Trigger
| Prop | Description |
| --- | --- |
| value | `string`<br> The unique key that associates the tab with a tab panel. |
| disabled | `boolean`<br> Whether the tab should be disabled. |
| Data attribute | Description |
| --- | --- |
| data-selected | Present when the trigger is selected. |
| data-disabled | Present when the trigger is disabled. |
| data-highlighted | Present when the trigger is highlighted. |
### Tabs.Content
| Prop | Description |
| --- | --- |
| value | `string`<br> The unique key that associates the tab panel with a tab. |
| forceMount | `boolean`<br> Used to force mounting when more control is needed. Useful when controlling animation with SolidJS animation libraries. |
| Data attribute | Description |
| --- | --- |
| data-selected | Present when the associated trigger is selected. |
## Rendered elements
| Component | Default rendered element |
| --- | --- |
| `Tabs` | `div` |
| `Tabs.List` | `div` |
| `Tabs.Trigger` | `button` |
| `Tabs.Indicator` | `div` |
| `Tabs.Content` | `div` |
## Accessibility
### Keyboard Interactions
| Key | Description |
| --- | --- |
| `Tab` | When focus moves onto the tabs, focuses the active trigger.<br>When a trigger is focused, moves focus to the active content. |
| `ArrowDown` | Moves focus to the next trigger in vertical orientation and activates its associated content. |
| `ArrowRight` | Moves focus to the next trigger in horizontal orientation and activates its associated content. |
| `ArrowUp` | Moves focus to the previous trigger in vertical orientation and activates its associated content. |
| `ArrowLeft` | Moves focus to the previous trigger in horizontal orientation and activates its associated content. |
| `Home` | Moves focus to the first trigger and activates its associated content. |
| `End` | Moves focus to the last trigger and activates its associated content. |
| `Enter` | In manual mode, when a trigger is focused, moves focus to its associated content. |
| `Space` | In manual mode, when a trigger is focused, moves focus to its associated content. |
Previous[←Switch](https://kobalte.dev/docs/core/components/switch)Next[Text Field→](https://kobalte.dev/docs/core/components/text-field)

View File

@ -0,0 +1,394 @@
# Text Field
A text input that allow users to input custom text entries with a keyboard.
## Import
```
Copyts
import { TextField } from "@kobalte/core/text-field";
// or
import { Root, Label, ... } from "@kobalte/core/text-field";
// or (deprecated)
import { TextField } from "@kobalte/core";
```
```
Copyts
import { TextField } from "@kobalte/core/text-field";
// or
import { Root, Label, ... } from "@kobalte/core/text-field";
// or (deprecated)
import { TextField } from "@kobalte/core";
```
## Features
- Built with a native `<input>` or `<textarea>` element.
- Visual and ARIA labeling support.
- Required and invalid states exposed to assistive technology via ARIA.
- Support for description and error message help text linked to the input via ARIA.
- Syncs with form reset events.
- Can be controlled or uncontrolled.
## Anatomy
The text field consists of:
- **TextField**: The root container for the text field.
- **TextField.Label**: The label that gives the user information on the text field.
- **TextField.Input**: The native HTML input of the text field, used for single line text.
- **TextField.TextArea**: The native HTML textarea of the text field, used for multiline text.
- **TextField.Description**: The description that gives the user more information on the text field.
- **TextField.ErrorMessage**: The error message that gives the user information about how to fix a validation error on the text field.
```
Copytsx
<TextField>
<TextField.Label />
<TextField.Input /> {/* or <TextField.TextArea /> */}
<TextField.Description />
<TextField.ErrorMessage />
</TextField>
```
```
Copytsx
<TextField>
<TextField.Label />
<TextField.Input /> {/* or <TextField.TextArea /> */}
<TextField.Description />
<TextField.ErrorMessage />
</TextField>
```
## Example
Favorite fruit
index.tsxstyle.css
```
Copytsx
import { TextField } from "@kobalte/core/text-field";
import "./style.css";
function App() {
return (
<TextField class="text-field">
<TextField.Label class="text-field__label">Favorite fruit</TextField.Label>
<TextField.Input class="text-field__input" />
</TextField>
);
}
```
```
Copytsx
import { TextField } from "@kobalte/core/text-field";
import "./style.css";
function App() {
return (
<TextField class="text-field">
<TextField.Label class="text-field__label">Favorite fruit</TextField.Label>
<TextField.Input class="text-field__input" />
</TextField>
);
}
```
## Usage
### Default value
An initial, uncontrolled value can be provided using the `defaultValue` prop.
Favorite fruit
```
Copytsx
<TextField defaultValue="Apple">
<TextField.Label>Favorite fruit</TextField.Label>
<TextField.Input />
</TextField>
```
```
Copytsx
<TextField defaultValue="Apple">
<TextField.Label>Favorite fruit</TextField.Label>
<TextField.Input />
</TextField>
```
### Controlled value
The `value` prop can be used to make the value controlled. The `onChange` event is fired when the user type into the input and receive the new value.
Favorite fruit
Your favorite fruit is: Apple.
```
Copytsx
import { createSignal } from "solid-js";
function ControlledExample() {
const [value, setValue] = createSignal("Apple");
return (
<>
<TextField value={value()} onChange={setValue}>
<TextField.Label>Favorite fruit</TextField.Label>
<TextField.Input />
</TextField>
<p>Your favorite fruit is: {value()}.</p>
</>
);
}
```
```
Copytsx
import { createSignal } from "solid-js";
function ControlledExample() {
const [value, setValue] = createSignal("Apple");
return (
<>
<TextField value={value()} onChange={setValue}>
<TextField.Label>Favorite fruit</TextField.Label>
<TextField.Input />
</TextField>
<p>Your favorite fruit is: {value()}.</p>
</>
);
}
```
### Multiline
Use the `TextField.TextArea` component instead of `TextField.Input` to create a multiline text field.
Favorite fruit
```
Copytsx
<TextField>
<TextField.Label>Favorite fruit</TextField.Label>
<TextField.TextArea />
</TextField>
```
```
Copytsx
<TextField>
<TextField.Label>Favorite fruit</TextField.Label>
<TextField.TextArea />
</TextField>
```
In addition, the `autoResize` prop can be used to make the textarea height adjust to it's content. Try typing in the text field below to see it in action.
Favorite fruit
```
Copytsx
<TextField>
<TextField.Label>Favorite fruit</TextField.Label>
<TextField.TextArea autoResize />
</TextField>
```
```
Copytsx
<TextField>
<TextField.Label>Favorite fruit</TextField.Label>
<TextField.TextArea autoResize />
</TextField>
```
### Description
The `TextField.Description` component can be used to associate additional help text with a text field.
Favorite fruit
Choose the fruit you like the most.
```
Copytsx
<TextField>
<TextField.Label>Favorite fruit</TextField.Label>
<TextField.Input />
<TextField.Description>Choose the fruit you like the most.</TextField.Description>
</TextField>
```
```
Copytsx
<TextField>
<TextField.Label>Favorite fruit</TextField.Label>
<TextField.Input />
<TextField.Description>Choose the fruit you like the most.</TextField.Description>
</TextField>
```
### Error message
The `TextField.ErrorMessage` component can be used to help the user fix a validation error. It should be combined with the `validationState` prop to semantically mark the text field as invalid for assistive technologies.
By default, it will render only when the `validationState` prop is set to `invalid`, use the `forceMount` prop to always render the error message (ex: for usage with animation libraries).
Favorite fruit
Hmm, I prefer apples.
```
Copytsx
import { createSignal } from "solid-js";
function ErrorMessageExample() {
const [value, setValue] = createSignal("Orange");
return (
<TextField
value={value()}
onChange={setValue}
validationState={value() !== "Apple" ? "invalid" : "valid"}
>
<TextField.Label>Favorite fruit</TextField.Label>
<TextField.Input />
<TextField.ErrorMessage>Hmm, I prefer apples.</TextField.ErrorMessage>
</TextField>
);
}
```
```
Copytsx
import { createSignal } from "solid-js";
function ErrorMessageExample() {
const [value, setValue] = createSignal("Orange");
return (
<TextField
value={value()}
onChange={setValue}
validationState={value() !== "Apple" ? "invalid" : "valid"}
>
<TextField.Label>Favorite fruit</TextField.Label>
<TextField.Input />
<TextField.ErrorMessage>Hmm, I prefer apples.</TextField.ErrorMessage>
</TextField>
);
}
```
### HTML forms
The text field `name` prop can be used for integration with HTML forms.
Favorite fruit
ResetSubmit
```
Copytsx
function HTMLFormExample() {
const onSubmit = (e: SubmitEvent) => {
// handle form submission.
};
return (
<form onSubmit={onSubmit}>
<TextField name="favorite-fruit">
<TextField.Label>Favorite fruit</TextField.Label>
<TextField.Input />
</TextField>
<div>
<button type="reset">Reset</button>
<button type="submit">Submit</button>
</div>
</form>
);
}
```
```
Copytsx
function HTMLFormExample() {
const onSubmit = (e: SubmitEvent) => {
// handle form submission.
};
return (
<form onSubmit={onSubmit}>
<TextField name="favorite-fruit">
<TextField.Label>Favorite fruit</TextField.Label>
<TextField.Input />
</TextField>
<div>
<button type="reset">Reset</button>
<button type="submit">Submit</button>
</div>
</form>
);
}
```
## API Reference
### TextField
`TextField` is equivalent to the `Root` import from `@kobalte/core/text-field` (and deprecated `TextField.Root`).
| Prop | Description |
| --- | --- |
| value | `string`<br> The controlled value of the text field to check. |
| defaultValue | `string`<br> The default value when initially rendered. Useful when you do not need to control the value. |
| onChange | `(value: string) => void`<br> Event handler called when the value of the textfield changes. |
| name | `string`<br> The name of the text field, used when submitting an HTML form. See [MDN](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#htmlattrdefname). |
| validationState | `'valid' | 'invalid'`<br> Whether the text field should display its "valid" or "invalid" visual styling. |
| required | `boolean`<br> Whether the user must fill the text field before the owning form can be submitted. |
| disabled | `boolean`<br> Whether the text field is disabled. |
| readOnly | `boolean`<br> Whether the text field items can be selected but not changed by the user. |
| Data attribute | Description |
| --- | --- |
| data-valid | Present when the text field is valid according to the validation rules. |
| data-invalid | Present when the text field is invalid according to the validation rules. |
| data-required | Present when the user must fill the text field before the owning form can be submitted. |
| data-disabled | Present when the text field is disabled. |
| data-readonly | Present when the text field is read only. |
`TextField.Label`, `TextField.Input`, `TextField.TextArea`, `TextField.Description` and `TextField.ErrorMesssage` share the same data-attributes.
### TextField.TextArea
| Prop | Description |
| --- | --- |
| autoResize | `boolean`<br> Whether the textarea should adjust its height when the value changes. |
| submitOnEnter | `boolean`<br> Whether the form should be submitted when the user presses the enter key. |
### TextField.ErrorMessage
| Prop | Description |
| --- | --- |
| forceMount | `boolean`<br> Used to force mounting when more control is needed. Useful when controlling animation with SolidJS animation libraries. |
## Rendered elements
| Component | Default rendered element |
| --- | --- |
| `TextField` | `div` |
| `TextField.Label` | `label` |
| `TextField.Input` | `input` |
| `TextField.TextArea` | `textarea` |
| `TextField.Description` | `div` |
| `TextField.ErrorMessage` | `div` |
Previous[←Tabs](https://kobalte.dev/docs/core/components/tabs)Next[Time Field→](https://kobalte.dev/docs/core/components/time-field)

View File

@ -0,0 +1,195 @@
# Toggle Button
A two-state button that can be either on (pressed) or off (not pressed).
## Import
```
Copyts
import { ToggleButton } from "@kobalte/core/toggle-button";
// or
import { Root } from "@kobalte/core/toggle-button";
// or (deprecated)
import { ToggleButton } from "@kobalte/core";
```
```
Copyts
import { ToggleButton } from "@kobalte/core/toggle-button";
// or
import { Root } from "@kobalte/core/toggle-button";
// or (deprecated)
import { ToggleButton } from "@kobalte/core";
```
## Features
- Native HTML `<button>`, `<a>`, and custom element type support.
- Exposed as a toggle button via the [WAI ARIA Button](https://www.w3.org/WAI/ARIA/apg/patterns/button/) design pattern.
- Mouse and touch event handling, and press state management.
- Keyboard event support for `Space` and `Enter` keys.
- Can be controlled or uncontrolled.
## Anatomy
The toggle button consists of:
- **ToggleButton:** the root container for a toggle button.
```
Copytsx
<ToggleButton />
```
```
Copytsx
<ToggleButton />
```
## Example
Volume On
index.tsxstyle.css
```
Copytsx
import { ToggleButton } from "@kobalte/core/toggle-button";
import { Show } from "solid-js";
import { VolumeOffIcon, VolumeOnIcon } from "some-icon-library";
import "./style.css";
function App() {
return (
<ToggleButton class="toggle-button" aria-label="Mute">
{state => (
<Show when={state.pressed()} fallback={<VolumeOnIcon />}>
<VolumeOffIcon />
</Show>
)}
</ToggleButton>
);
}
```
```
Copytsx
import { ToggleButton } from "@kobalte/core/toggle-button";
import { Show } from "solid-js";
import { VolumeOffIcon, VolumeOnIcon } from "some-icon-library";
import "./style.css";
function App() {
return (
<ToggleButton class="toggle-button" aria-label="Mute">
{state => (
<Show when={state.pressed()} fallback={<VolumeOnIcon />}>
<VolumeOffIcon />
</Show>
)}
</ToggleButton>
);
}
```
## Usage
### Default pressed
An initial, uncontrolled value can be provided using the `defaultPressed` prop.
Volume Off
```
Copytsx
<ToggleButton defaultPressed>...</ToggleButton>
```
```
Copytsx
<ToggleButton defaultPressed>...</ToggleButton>
```
### Controlled pressed
The `pressed` prop can be used to make the pressed state controlled. The `onChange` event is fired when the user toggle the button, and receives the new value.
Volume On
The microphone is active.
```
Copytsx
import { createSignal } from "solid-js";
function ControlledExample() {
const [pressed, setPressed] = createSignal(false);
return (
<>
<ToggleButton pressed={pressed()} onChange={setPressed}>
...
</ToggleButton>
<p>The microphone is {pressed() ? "muted" : "active"}.</p>
</>
);
}
```
```
Copytsx
import { createSignal } from "solid-js";
function ControlledExample() {
const [pressed, setPressed] = createSignal(false);
return (
<>
<ToggleButton pressed={pressed()} onChange={setPressed}>
...
</ToggleButton>
<p>The microphone is {pressed() ? "muted" : "active"}.</p>
</>
);
}
```
## API Reference
### ToggleButton
`ToggleButton` is equivalent to the `Root` import from `@kobalte/core/toggle-button` (and deprecated `ToggleButton.Root`).
`ToggleButton` consists of [Button](https://kobalte.dev/docs/core/components/button) and additional props.
| Prop | Description |
| --- | --- |
| pressed | `boolean`<br> The controlled pressed state of the toggle button. |
| defaultPressed | `boolean`<br> The default pressed state when initially rendered. Useful when you do not need to control the pressed state. |
| onChange | `(pressed: boolean) => void`<br> Event handler called when the pressed state of the toggle button changes. |
| children | `JSX.Element | (state: ToggleButtonState) => JSX.Element`<br> The children of the toggle button. Can be a `JSX.Element` or a _render prop_ for having access to the internal state. |
| Render Prop | Description |
| --- | --- |
| pressed | `Accessor<boolean>`<br> Whether the toggle button is on (pressed) or off (not pressed). |
| Data attribute | Description |
| --- | --- |
| data-pressed | Present when the toggle button is on (pressed). |
## Rendered elements
| Component | Default rendered element |
| --- | --- |
| `ToggleButton` | `button` |
## Accessibility
### Keyboard Interactions
| Key | Description |
| --- | --- |
| `Space` | Activates/deactivates the toggle button. |
| `Enter` | Activates/deactivates the toggle button. |
Previous[←Toast](https://kobalte.dev/docs/core/components/toast)Next[Toggle Group→](https://kobalte.dev/docs/core/components/toggle-group)

View File

@ -0,0 +1,295 @@
# Toggle Group
A set of two-state buttons that can be toggled on (pressed) or off (not pressed).
## Import
```
Copyts
import { ToggleGroup } from "@kobalte/core/toggle-group";
// or
import { Root, Item, ... } from "@kobalte/core/toggle-group";
// or (deprecated)
import { ToggleGroup } from "@kobalte/core";
```
```
Copyts
import { ToggleGroup } from "@kobalte/core/toggle-group";
// or
import { Root, Item, ... } from "@kobalte/core/toggle-group";
// or (deprecated)
import { ToggleGroup } from "@kobalte/core";
```
## Features
- Supports horizontal/vertical orientation.
- Keyboard event support for `Space` and `Enter` keys.
- Can be controlled or uncontrolled.
## Anatomy
The toggle group consists of:
- **ToggleGroup:** the root container for a toggle group.
The toggle item consists of:
- **ToggleGroup.Item:** the root container for a toggle button.
```
Copytsx
<ToggleGroup>
<ToggleGroup.Item />
</ToggleGroup>
```
```
Copytsx
<ToggleGroup>
<ToggleGroup.Item />
</ToggleGroup>
```
## Example
BoldItalicUnderline
index.tsxstyle.css
```
Copytsx
import {ToggleButton} from "@kobalte/core/toggle-group";
import {BoldIcon, ItalicIcon, UnderlineIcon} from "some-icon-library";
import "./style.css";
<ToggleGroup class="toggle-group">
<ToggleGroup.Item class="toggle-group__item" value="bold" aria-label="Bold">
<BoldIcon/>
</ToggleGroup.Item>
<ToggleGroup.Item class="toggle-group__item" value="italic" aria-label="Italic">
<ItalicIcon/>
</ToggleGroup.Item>
<ToggleGroup.Item class="toggle-group__item" value="underline" aria-label="Underline">
<UnderlineIcon/>
</ToggleGroup.Item>
</ToggleGroup>
```
```
Copytsx
import {ToggleButton} from "@kobalte/core/toggle-group";
import {BoldIcon, ItalicIcon, UnderlineIcon} from "some-icon-library";
import "./style.css";
<ToggleGroup class="toggle-group">
<ToggleGroup.Item class="toggle-group__item" value="bold" aria-label="Bold">
<BoldIcon/>
</ToggleGroup.Item>
<ToggleGroup.Item class="toggle-group__item" value="italic" aria-label="Italic">
<ItalicIcon/>
</ToggleGroup.Item>
<ToggleGroup.Item class="toggle-group__item" value="underline" aria-label="Underline">
<UnderlineIcon/>
</ToggleGroup.Item>
</ToggleGroup>
```
## Usage
### Default pressed
An initial, uncontrolled value can be provided using the `defaultValue` prop.
BoldItalicUnderline
```
Copytsx
<ToggleGroup defaultValue="underline">
<ToggleGroup.Item value="bold" aria-label="Bold">
<BoldIcon />
</ToggleGroup.Item>
<ToggleGroup.Item value="italic" aria-label="Italic">
<ItalicIcon />
</ToggleGroup.Item>
<ToggleGroup.Item value="underline" aria-label="Underline">
<UnderlineIcon />
</ToggleGroup.Item>
</ToggleGroup>
```
```
Copytsx
<ToggleGroup defaultValue="underline">
<ToggleGroup.Item value="bold" aria-label="Bold">
<BoldIcon />
</ToggleGroup.Item>
<ToggleGroup.Item value="italic" aria-label="Italic">
<ItalicIcon />
</ToggleGroup.Item>
<ToggleGroup.Item value="underline" aria-label="Underline">
<UnderlineIcon />
</ToggleGroup.Item>
</ToggleGroup>
```
### Controlled pressed
The `value` prop can be used to make the pressed state controlled. The `onChange` event is fired when the user toggle the button, and receives the new value.
BoldItalicUnderline
Your text style is: **bold**.
```
Copytsx
import { createSignal } from "solid-js";
function ControlledExample() {
const [value, setValue] = createSignal("underline");
return (
<>
<ToggleGroup value={value()} onChange={setValue}>
...
</ToggleGroup>
<p>Your text style is: {value()}.</p>
</>
);
}
```
```
Copytsx
import { createSignal } from "solid-js";
function ControlledExample() {
const [value, setValue] = createSignal("underline");
return (
<>
<ToggleGroup value={value()} onChange={setValue}>
...
</ToggleGroup>
<p>Your text style is: {value()}.</p>
</>
);
}
```
### Multiple selection
The `multiple` prop can be used to create a select that allow multi-selection.
BoldItalicUnderline
```
Copytsx
import { createSignal } from "solid-js";
function MultipleSelectionExample() {
const [values, setValues] = createSignal(["bold", "underline"]);
return (
<ToggleGroup class="toggle-group" value={values()} onChange={setValues}>
<ToggleGroup.Item class="toggle-group__item" value="bold" aria-label="Bold">
<BoldIcon />
</ToggleGroup.Item>
<ToggleGroup.Item class="toggle-group__item" value="italic" aria-label="Italic">
<ItalicIcon />
</ToggleGroup.Item>
<ToggleGroup.Item class="toggle-group__item" value="underline" aria-label="Underline">
<UnderlineIcon />
</ToggleGroup.Item>
</ToggleGroup>
);
}
```
```
Copytsx
import { createSignal } from "solid-js";
function MultipleSelectionExample() {
const [values, setValues] = createSignal(["bold", "underline"]);
return (
<ToggleGroup class="toggle-group" value={values()} onChange={setValues}>
<ToggleGroup.Item class="toggle-group__item" value="bold" aria-label="Bold">
<BoldIcon />
</ToggleGroup.Item>
<ToggleGroup.Item class="toggle-group__item" value="italic" aria-label="Italic">
<ItalicIcon />
</ToggleGroup.Item>
<ToggleGroup.Item class="toggle-group__item" value="underline" aria-label="Underline">
<UnderlineIcon />
</ToggleGroup.Item>
</ToggleGroup>
);
}
```
## API Reference
### ToggleGroup
`ToggleGroup` is equivalent to the `Root` import from `@kobalte/core/toggle-group` (and deprecated `ToggleGroup.Root`).
| Prop | Description |
| --- | --- |
| value | `string | string[]`<br> The controlled pressed state of the toggle button. |
| defaultValue | `string | string[]`<br> The default pressed state when initially rendered. Useful when you do not need to control the pressed state. |
| onChange | `(value: string | string[]) => void`<br> Event handler called when the pressed state of an item changes. |
| multiple | `boolean`<br> Whether the toggle group allows multi-selection. |
| orientation | `'horizontal' | 'vertical'`<br> The orientation of the toggle group. |
| disabled | `boolean`<br> Whether toggle group should be disabled. |
| Data attribute | Description |
| --- | --- |
| data-orientation='horizontal' | Present when the separator has horizontal orientation. |
| data-orientation='vertical' | Present when the separator has vertical orientation. |
### ToggleGroup.Item
| Prop | Description |
| --- | --- |
| value | `string`<br> A unique value for the item. |
| disabled | `boolean`<br> Whether the item is disabled. |
| children | `JSX.Element | (state: ToggleButtonState) => JSX.Element`<br> The children of the item. Can be a `JSX.Element` or a _render prop_ for having access to the internal state. |
| Render Prop | Description |
| --- | --- |
| pressed | `Accessor<boolean>`<br> Whether the toggle button is on (pressed) or off (not pressed). |
| Data attribute | Description |
| --- | --- |
| data-orientation='horizontal' | Present when the separator has horizontal orientation. |
| data-orientation='vertical' | Present when the separator has vertical orientation. |
| data-disabled | Present when the accordion item is disabled. |
| data-pressed | Present when the toggle button is on (pressed). |
## Rendered elements
| Component | Default rendered element |
| --- | --- |
| `ToggleGroup` | `div` |
| `ToggleGroup.Item` | `button` |
## Accessibility
### Keyboard Interactions
| Key | Description |
| --- | --- |
| `Tab` | Move focus to either the pressed item or the first item in the group. |
| `ArrowDown` | If orientation is vertical, moves focus to the next item. |
| `ArrowRight` | If orientation is horizontal, Moves focus to the next item. |
| `ArrowUp` | If orientation is vertical, moves focus to the previous item. |
| `ArrowLeft` | If orientation is vertical, moves focus to the previous item. |
| `Home` | Moves focus to the first item. |
| `End` | Moves focus to the last item. |
| `Enter` | Activates/deactivates the item. |
| `Space` | Activates/deactivates the item. |
Previous[←Toggle Button](https://kobalte.dev/docs/core/components/toggle-button)Next[Tooltip→](https://kobalte.dev/docs/core/components/tooltip)

View File

@ -0,0 +1,343 @@
# Tooltip
A popup that displays information related to an element when the element receives keyboard focus or the mouse hovers over it.
## Import
```
Copyts
import { Tooltip } from "@kobalte/core/tooltip";
// or
import { Root, Trigger, ... } from "@kobalte/core/tooltip";
// or (deprecated)
import { Tooltip } from "@kobalte/core";
```
```
Copyts
import { Tooltip } from "@kobalte/core/tooltip";
// or
import { Root, Trigger, ... } from "@kobalte/core/tooltip";
// or (deprecated)
import { Tooltip } from "@kobalte/core";
```
## Features
- Exposed as a tooltip to assistive technology via ARIA.
- Opens when the trigger is focused or hovered.
- Closes when the trigger is activated or when pressing escape.
- Only one tooltip shows at a time.
- Labeling support for screen readers via `aria-describedby`.
- Custom show and hide delay support.
- Matches native tooltip behavior with delay on hover of first tooltip and no delay on subsequent tooltips.
## Anatomy
The tooltip consists of:
- **Tooltip:** The root container for a tooltip.
- **Tooltip.Trigger:** The button that toggles the tooltip.
- **Tooltip.Portal:** Portals its children into the `body` when the tooltip is open.
- **Tooltip.Content:** Contains the content to be rendered when the tooltip is open.
- **Tooltip.Arrow:** An optional arrow element to render alongside the tooltip.
```
Copytsx
<Tooltip>
<Tooltip.Trigger />
<Tooltip.Portal>
<Tooltip.Content>
<Tooltip.Arrow />
</Tooltip.Content>
</Tooltip.Portal>
</Tooltip>
```
```
Copytsx
<Tooltip>
<Tooltip.Trigger />
<Tooltip.Portal>
<Tooltip.Content>
<Tooltip.Arrow />
</Tooltip.Content>
</Tooltip.Portal>
</Tooltip>
```
## Example
Trigger
index.tsxstyle.css
```
Copytsx
import { Tooltip } from "@kobalte/core/tooltip";
import "./style.css";
function App() {
return (
<Tooltip>
<Tooltip.Trigger class="tooltip__trigger">Trigger</Tooltip.Trigger>
<Tooltip.Portal>
<Tooltip.Content class="tooltip__content">
<Tooltip.Arrow />
<p>Tooltip content</p>
</Tooltip.Content>
</Tooltip.Portal>
</Tooltip>
);
}
```
```
Copytsx
import { Tooltip } from "@kobalte/core/tooltip";
import "./style.css";
function App() {
return (
<Tooltip>
<Tooltip.Trigger class="tooltip__trigger">Trigger</Tooltip.Trigger>
<Tooltip.Portal>
<Tooltip.Content class="tooltip__content">
<Tooltip.Arrow />
<p>Tooltip content</p>
</Tooltip.Content>
</Tooltip.Portal>
</Tooltip>
);
}
```
## Usage
### Default open
An initial, uncontrolled open value can be provided using the `defaultOpen` prop.
```
Copytsx
<Tooltip defaultOpen>...</Tooltip>
```
```
Copytsx
<Tooltip defaultOpen>...</Tooltip>
```
### Controlled open
The `open` prop can be used to make the open state controlled. The `onOpenChange` event is fired when the user presses the trigger, close button or interact outside, and receives the new value.
Tooltip is not showing.
Trigger
```
Copytsx
import { createSignal } from "solid-js";
function ControlledExample() {
const [open, setOpen] = createSignal(false);
return (
<>
<p>Tooltip is {open() ? "showing" : "not showing"}.</p>
<Tooltip open={open()} onOpenChange={setOpen}>
<Tooltip.Trigger>Trigger</Tooltip.Trigger>
<Tooltip.Portal>
<Tooltip.Content>
<Tooltip.Arrow />
<p>Tooltip content</p>
</Tooltip.Content>
</Tooltip.Portal>
</Tooltip>
</>
);
}
```
```
Copytsx
import { createSignal } from "solid-js";
function ControlledExample() {
const [open, setOpen] = createSignal(false);
return (
<>
<p>Tooltip is {open() ? "showing" : "not showing"}.</p>
<Tooltip open={open()} onOpenChange={setOpen}>
<Tooltip.Trigger>Trigger</Tooltip.Trigger>
<Tooltip.Portal>
<Tooltip.Content>
<Tooltip.Arrow />
<p>Tooltip content</p>
</Tooltip.Content>
</Tooltip.Portal>
</Tooltip>
</>
);
}
```
### Origin-aware animations
We expose a CSS custom property `--kb-tooltip-content-transform-origin` which can be used to animate the content from its computed origin.
```
Copycss
/* style.css */
.tooltip__content {
transform-origin: var(--kb-tooltip-content-transform-origin);
animation: contentHide 250ms ease-in forwards;
}
.tooltip__content[data-expanded] {
animation: contentShow 250ms ease-out;
}
@keyframes contentShow {
from {
opacity: 0;
transform: scale(0.96);
}
to {
opacity: 1;
transform: scale(1);
}
}
@keyframes contentHide {
from {
opacity: 1;
transform: scale(1);
}
to {
opacity: 0;
transform: scale(0.96);
}
}
```
```
Copycss
/* style.css */
.tooltip__content {
transform-origin: var(--kb-tooltip-content-transform-origin);
animation: contentHide 250ms ease-in forwards;
}
.tooltip__content[data-expanded] {
animation: contentShow 250ms ease-out;
}
@keyframes contentShow {
from {
opacity: 0;
transform: scale(0.96);
}
to {
opacity: 1;
transform: scale(1);
}
}
@keyframes contentHide {
from {
opacity: 1;
transform: scale(1);
}
to {
opacity: 0;
transform: scale(0.96);
}
}
```
## API Reference
### Tooltip
`Tooltip` is equivalent to the `Root` import from `@kobalte/core/tooltip` (and deprecated `Tooltip.Root`).
| Prop | Description |
| --- | --- |
| open | `boolean`<br> The controlled open state of the tooltip. |
| defaultOpen | `boolean`<br> The default open state when initially rendered. Useful when you do not need to control the open state. |
| onOpenChange | `(open: boolean) => void`<br> Event handler called when the open state of the tooltip changes. |
| triggerOnFocusOnly | `boolean`<br> Whether to open the tooltip only when the trigger is focused. By default, opens for both focus and hover. |
| openDelay | `number`<br> The duration from when the mouse enters the trigger until the tooltip opens. |
| skipDelayDuration | `number`<br> The duration from when the mouse leaves the trigger or content and moves to another tooltip trigger or content without incurring another openDelay |
| closeDelay | `number`<br> The duration from when the mouse leaves the trigger or content until the tooltip closes. |
| ignoreSafeArea | `boolean`<br> Whether to close the tooltip even if the user cursor is inside the safe area between the trigger and tooltip. |
| id | `string`<br> A unique identifier for the component. The id is used to generate id attributes for nested components. If no id prop is provided, a generated id will be used. |
| forceMount | `boolean`<br> Used to force mounting the tooltip (portal and content) when more control is needed. Useful when controlling animation with SolidJS animation libraries. |
`Tooltip` also accepts the following props to customize the placement of the `Tooltip.Content`.
| Prop | Description |
| --- | --- |
| getAnchorRect | `(anchor?: HTMLElement) => AnchorRect | undefined`<br> Function that returns the anchor element's DOMRect. |
| placement | `Placement`<br> The placement of the tooltip. |
| gutter | `number`<br> The distance between the tooltip and the trigger/anchor element. By default, it's 0 plus half of the arrow offset, if it exists. |
| shift | `number`<br> The skidding of the tooltip along the anchor element. |
| flip | `boolean | string`<br> Controls the behavior of the tooltip when it overflows the viewport:<br> \- If a `boolean`, specifies whether the tooltip should flip to the opposite side when it overflows.<br> \- If a `string`, indicates the preferred fallback placements when it overflows.<br>The placements must be spaced-delimited, e.g. "top left". |
| slide | `boolean`<br> Whether the tooltip should slide when it overflows. |
| overlap | `boolean`<br> Whether the tooltip can overlap the anchor element when it overflows. |
| sameWidth | `boolean`<br> Whether the tooltip should have the same width as the anchor element. This will be exposed to CSS as `--kb-popper-anchor-width`. |
| fitViewport | `boolean`<br> Whether the tooltip should fit the viewport. If this is set to true, the tooltip content will have `maxWidth` and `maxHeight` set to the viewport size. This will be exposed to CSS as `--kb-popper-available-width` and `--kb-popper-available-height`. |
| hideWhenDetached | `boolean`<br> Whether to hide the tooltip when the anchor element becomes occluded. |
| detachedPadding | `number`<br> The minimum padding in order to consider the anchor element occluded. |
| arrowPadding | `number`<br> The minimum padding between the arrow and the tooltip corner. |
| overflowPadding | `number`<br> The minimum padding between the tooltip and the viewport edge. This will be exposed to CSS as `--kb-popper-overflow-padding`. |
### Tooltip.Trigger
| Data attribute | Description |
| --- | --- |
| data-expanded | Present when the tooltip is open. |
| data-closed | Present when the tooltip is close. |
`Tooltip.Content` and `Tooltip.Arrow` share the same data-attributes.
### Tooltip.Content
The popper positioner will copy the same `z-index` as the `Tooltip.Content`.
| Prop | Description |
| --- | --- |
| onEscapeKeyDown | `(event: KeyboardEvent) => void`<br> Event handler called when the escape key is down. It can be prevented by calling `event.preventDefault`. |
| onPointerDownOutside | `(event: PointerDownOutsideEvent) => void`<br> Event handler called when a pointer event occurs outside the bounds of the component. It can be prevented by calling `event.preventDefault`. |
### Tooltip.Arrow
| Prop | Description |
| --- | --- |
| size | `number`<br> The size of the arrow. |
## Rendered elements
| Component | Default rendered element |
| --- | --- |
| `Tooltip` | none |
| `Tooltip.Trigger` | `button` |
| `Tooltip.Portal` | `Portal` |
| `Tooltip.Content` | `div` |
| `Tooltip.Arrow` | `div` |
## Accessibility
### Keyboard Interactions
| Key | Description |
| --- | --- |
| `Tab` | Opens/closes the tooltip without delay. |
| `Space` | When open, closes the tooltip without delay. |
| `Enter` | When open, closes the tooltip without delay. |
| `Esc` | When open, closes the tooltip without delay. |
Previous[←Toggle Group](https://kobalte.dev/docs/core/components/toggle-group)Next[I18nProvider→](https://kobalte.dev/docs/core/components/i18n-provider)

View File

@ -1,381 +0,0 @@
[**Advisory boards arent only for executives. Join the LogRocket Content Advisory Board today →**](https://lp.logrocket.com/blg/content-advisory-board-signup)
[![LogRocket blog logo](https://blog.logrocket.com/wp-content/themes/logrocket/assets/logrocket-logo.png)](https://logrocket.com/)
2025-10-03
1610
#ai
Chizaram Ken
207953
102
![](https://blog.logrocket.com/wp-content/uploads/2023/04/logrocket-logo-1.png)
## See how LogRocket's Galileo AI surfaces the most severe issues for you
### No signup required
Check it out
Galileo AI Overview - May 2025
![Video Thumbnail](https://embed-ssl.wistia.com/deliveries/d13588ad6864cb4841845467c9b8feb8.webp?image_crop_resized=1920x1079)
1:15
Click for sound
You ask Claude Code or Cursor about a shadcn/ui component, and itll confidently spit out props that dont exist, dust off patterns from 2023, or just flat-out make things up.
![I Tried Shadcn CLI 3.0 — Heres What I Learned](https://blog.logrocket.com/wp-content/uploads/2025/10/ShadCN.png)
Most of the time, this comes down to version changes. shadcn/ui keeps evolving, new props, updated requirements, and agents often lean on older docs or outdated patterns.
Other times, its simply the AI guessing. This is the not-so-smart side of AI: it wont admit “I dont know,” so it stitches together whatever scraps it half-remembers from training instead of the actual component code.
Case in point: your agent might suggest `<Button loading={true}>` even though shadcn/uis Button has no `loading` prop. Its pulling from some other UI library in the background.
The truth is, it guesses because it has almost zero library context. And thats exactly why were going to look at the shadcn/ui [MCP server](https://ui.shadcn.com/docs/mcp), to give your agent real, live access to the component library instead of making it wing it.
Before we dive in, lets set the stage. The goal of this article is simple: show you how to use the shadcn/ui MCP Server in your workflow so your AI agent stops generating broken components. With the right setup, youll get reliable, up-to-date ShadCN code instead of outdated patterns or random guesses.
### 🚀 Sign up for The Replay newsletter
[**The Replay**](https://blog.logrocket.com/the-replay-archive/) is a weekly newsletter for dev and engineering leaders.
Delivered once a week, it's your curated guide to the most important conversations around frontend dev, emerging AI tools, and the state of modern software.
Fields marked with an \* are required
Email \*
If you are a human seeing this field, please leave it empty.
## **ShadCN MCP Server**
The ShadCN MCP (Model Context Protocol) server acts as a link between AI agents and component registries, and it fundamentally changes how AI agents interact with component libraries. Unlike [other ShadCN MCPs](https://github.com/Jpisnice/shadcn-ui-mcp-server) that provide only data for ShadCN components, this official MCP server provides more recent access to:
### **Live component access**
AI assistants gain direct connection to current component specifications, ensuring they always work with the latest versions and configurations.
### **Registry integration**
The server connects to multiple component sources, this includes the official shadcn/ui registry, giving your AI agents access to more detailed data of components needed for your project. So, you are no longer limited to just ShadCN.
### **Accurate installation**
AI Agents can now interpret conversational prompts like “add a login form” or “create a contact form using Shadcn components” and translate them into proper registry commands and needed installations.
### **Better component selection**
The server enables AI Agents to search the available components and make more informed decisions about which components best fit specific requirements.
![](https://blog.logrocket.com/wp-content/uploads/2025/09/image7.png)
## **Quick setup**
Setting up the Shadcn MCP server is a straightforward process for any major AI coding environment. We will be using Claude-code for this, go ahead and install it using the command below:
```bash
curl -fsSL https://claude.ai/install.sh | sh
```
Now open up your CLI, prompt `Claude`. You should know Claude is available if you see this:
![](https://blog.logrocket.com/wp-content/uploads/2025/09/image11.png)
Now go ahead and install the MCP server by running the command below in your project directory:
```bash
npx shadcn@latest mcp init --client claude
```
![](https://blog.logrocket.com/wp-content/uploads/2025/09/image3.png)
Go ahead to restart Claude Code now. If it was properly installed, you should see this:
![](https://blog.logrocket.com/wp-content/uploads/2025/09/image9.png)
After that, you can use the `/mcp` command, and you will be able to see the MCP tools:
![](https://blog.logrocket.com/wp-content/uploads/2025/09/image4_0a7fb3.png)
There are about seven tools available. You can immediately start using prompts like “Show me all available components in the ShadCN registry” or “Add the button, dialog, and card components to my project.”
### **Configuration for other environments like Cursor**
Add the MCP server configuration to your projects MCP configuration file:
```json
{
"mcpServers": {
"shadcn": {
"command": "npx",
"args": ["shadcn@latest", "mcp"]
}
}
}
```
### **Multi-registry support**
Configure additional registries in your `components.json` to access private or third-party component libraries:
```json
{
"registries": {
"@acme": "https://registry.acme.com/{name}.json",
"@internal": {
"url": "https://internal.company.com/{name}.json",
"headers": {
"Authorization": "Bearer ${REGISTRY_TOKEN}"
}
}
}
}
```
At this point, we can go ahead to build something that actually utilizes a whole lot of ShanCn components, like a kanban board.
## **Building Kanban board**
We want to build a Kanban board for my article writing workflow at LogRocket Blog. Heres my complete process from initial research to publication:
### **Topic research**
- Search for topics Im familiar with and passionate about
- Check the LogRocket Blog archives to see if the topic has been covered
- Assess coverage depth is there room for a fresh perspective or deeper dive?
### **Content strategy**
- Define learning outcomes What will readers gain from investing 8 minutes in this piece?
- Determine tone and approach Should this be highly technical and direct, or include light humor to ease the readers tension?
- Research competitor content and identify unique angles
### **Draft an outline**
- Draft a detailed article structure
- Submit the outline to the manager for review and approval
- Gain approval and work on considerable feedback from the manager
### **Create content**
- Write the approved article following the outlined structure(Sometimes you make a lot of tweaks for better quality)
- Ensure technical accuracy and readers engagement
- Self-review for clarity and smooth flow
### **Editorial process**
- Submit to the editing team for review
- Address comments and suggestions from the editor (theres always at least one!)
- Finalize revisions and prepare for publication
### **Publication & launch**
- Article goes live on LogRocket Blog
- Monitor initial reader engagement and feedback
Lets go ahead and create the Kanban board for this workflow. Here is a detailed prompt that embodies our workflow:
```text
Build a Kanban board component for my LogRocket Blog article writing workflow. I need a drag-and-drop board with 6 columns representing my workflow stages:
- Topic Research - For initial topic exploration and validation
- Content Strategy - For planning learning outcomes and approach
- Draft Outline - For creating and getting approval on article structure
- Create Content - For writing the actual article
- Editorial Process - For editing, revisions, and feedback
- Publication & Launch - For live articles and monitoring
Each column should:
- Display the stage name clearly
- Show a count of cards in that column
- Allow drag-and-drop functionality between columns
- Support adding new article cards
Each article card should include:
- Article title/topic
- Brief description or notes
- Priority indicator (high/medium/low)
- Due date or target timeline
- Current status within that stage
Please use ShadCN components like Card, Badge, Button, and any drag-and-drop utilities available, if you do not find the exact names of these components use something components that are very similar. Make it clean, professional, and suitable for a content writer's daily workflow. Include sample article cards in different stages to demonstrate the workflow.
```
We will feed this to Claude-code and see what the result is like. Right away, it goes to work:
![](https://blog.logrocket.com/wp-content/uploads/2025/09/image1.png)
Were done, I guess:
![](https://blog.logrocket.com/wp-content/uploads/2025/09/image10.png)
This is what the first result looks like:
![](https://blog.logrocket.com/wp-content/uploads/2025/09/image8.png)
I feel Claude omitted the point where ShadCN components need CSS variables defined. Will tell it to fix that.
* * *
[![](https://blog.logrocket.com/wp-content/uploads/2023/04/Screen-Shot-2023-04-05-at-3.19.07-PM.png)\\
\\
**Over 200k developers use LogRocket to create better digital experiences** \\
\\
![](https://blog.logrocket.com/wp-content/uploads/2022/08/rocket-button-icon.png)Learn more →](https://lp.logrocket.com/blg/learn-more)
* * *
![](https://blog.logrocket.com/wp-content/uploads/2025/09/image5.png)
Here is what the UI looks like now:
![](https://blog.logrocket.com/wp-content/uploads/2025/09/image2.gif)
I wanted to see how different this would have been without the MCP server, so I did another test with Gemini CLI. Here was how the first result came out:
![](https://blog.logrocket.com/wp-content/uploads/2025/09/image12.png)
Here is the final result:
![](https://blog.logrocket.com/wp-content/uploads/2025/09/image6.gif)
Same prompt, same number of iterations, but not the same results, and that exactly explains why you should utilize the ShadCN MCP server for your next project, thats if you choose to use ShadCN as your UI library, though.
## **Troubleshooting**
Here are some common issues you might encounter:
### **MCP Server not responding**
When your MCP server isnt picking up your prompts:
- **Check your configuration** Make sure the MCP server is properly set up in your `.mcp.json` file and enabled in your client
- **Restart your client** After any configuration changes, restart Claude Code, Cursor, Windsurf, or VS Code completely
- **Verify ShadCN installation** Ensure you have ShadCN properly installed in your project directory
- **Test network access** Confirm you can reach the configured registries from your development environment
### **Registry access problems**
If components arent loading from your registries:
- **Double-check components.json** Verify your registry URLs are formatted correctly and accessible
- **Test authentication** Make sure environment variables are properly set for private registries in your `.env.local`
- **Confirm registry status** Check that the registry is online and responding
- **Validate namespace syntax** Ensure youre using the correct `@namespace/component` format
### **Installation failures**
When components refuse to install:
- **Verify project setup** Confirm you have a valid `components.json` file in your project root
- **Check directory paths** Make sure target directories exist and are writable
- **Review permissions** Ensure you have write permissions for component directories
### **No tools available**
If youre seeing “No tools or prompts” messages:
- **Clear NPX cache** Run `npx clear-npx-cache` to refresh cached packages
- **Re-enable MCP Server** Try disabling and re-enabling the MCP server in your client settings
- **Check logs** In Cursor or Windsurf, go to View → Output and select “MCP: project-” from the dropdown to see detailed logs
Based on the shadcn/ui MCP Server Documentation and troubleshooting experience. There are other ShadCN MCP servers; you can try [this as well](https://github.com/Jpisnice/shadcn-ui-mcp-server).
## **Conclusion**
The gap between AI agents with and without access to live component docs is huge. With the shadcn/ui MCP server, Claude Code delivered accurate, working components aligned with the latest specs. Without it, Gemini CLI slipped into outdated patterns and even made up props that never existed.
This goes beyond convenience; its about reliability. The MCP server cuts out the cycle of AI-generated code that looks right but fails at runtime because its based on guesswork, not facts.
For developers using shadcn/ui, the setup takes just a few minutes and can save hours of debugging broken components. With MCP in place, your AI coding assistant stops guessing and starts acting like a partner that actually understands the library.
Learn more about MCPs here:
- [Understanding Anthropics Model Context Protocol (MCP)](https://blog.logrocket.com/understanding-anthropic-model-context-protocol-mcp/)
- [The top 15 MCP servers for your AI projects](https://blog.logrocket.com/top-15-mcp-servers-ai-projects/)
- [#ai](https://blog.logrocket.com/tag/ai/)
![](https://blog.logrocket.com/wp-content/uploads/2022/06/footer-cta-dots-left.png)![](https://blog.logrocket.com/wp-content/uploads/2022/06/footer-cta-dots-right.png)
![](https://blog.logrocket.com/wp-content/uploads/2022/09/logrocket-logo-frontend-analytics.png)
## Stop guessing about your digital experience with LogRocket
[Get started for free](https://lp.logrocket.com/blg/signup)
#### Recent posts:
[![the replay march 18](https://blog.logrocket.com/wp-content/uploads/2026/03/The-Replay-Graphic-20.png)\\
**The Replay (3/18/26): Hiring in the AI era, coding isnt dead, and more**](https://blog.logrocket.com/the-replay-3-18-26/)
Discover whats new in The Replay, LogRockets newsletter for dev and engineering leaders, in the March 18th issue.
[![](https://secure.gravatar.com/avatar/bdb8fc1c58c8a14e8c52f80c0ff964372237e773db8d0d1a179151099f28c358?s=50&d=mm&r=g)](https://blog.logrocket.com/author/matthewmaccormack/)[Matt MacCormack](https://blog.logrocket.com/author/matthewmaccormack/)
Mar 18, 2026 ⋅ 29 sec read
[![ken pickering ai hiring quote card](https://blog.logrocket.com/wp-content/uploads/2026/03/3.18-Ken-Pickering-Engineering-Hiring.png)\\
**Thinking beats coding: How to hire the right engineers in the AI era**](https://blog.logrocket.com/how-to-hire-the-right-engineers-in-the-ai-era/)
A CTO outlines his case for how leaders should prioritize complex thinking over framework knowledge when hiring engineers for the AI era.
[![](https://blog.logrocket.com/wp-content/uploads/2026/01/ken_pickering-150x150.jpeg)](https://blog.logrocket.com/author/ken_pickering/)[Ken Pickering](https://blog.logrocket.com/author/ken_pickering/)
Mar 18, 2026 ⋅ 4 min read
[![](https://blog.logrocket.com/wp-content/uploads/2026/03/Exploring-Vercels-JSON-Render-build-dynamic-UI-from-structured-data.png)\\
**Exploring Vercels JSON Render: build dynamic UI from structured data**](https://blog.logrocket.com/vercel-json-render-dynamic-ui/)
Build dynamic, AI-generated UI safely with Vercels JSON Render using structured JSON, validated components, and React.
[![](https://blog.logrocket.com/wp-content/uploads/2021/01/AirBrush_20210107121828-150x150.jpg)](https://blog.logrocket.com/author/emmanueljohn/)[Emmanuel John](https://blog.logrocket.com/author/emmanueljohn/)
Mar 17, 2026 ⋅ 11 min read
[![](https://blog.logrocket.com/wp-content/uploads/2026/03/Stop-wasting-money-on-AI-10-ways-to-cut-token-usage.png)\\
**Stop wasting money on AI: 10 ways to cut token usage**](https://blog.logrocket.com/stop-wasting-ai-tokens-10-ways-to-reduce-usage/)
Learn practical techniques to reduce token usage in LLM applications and build more cost-efficient, scalable AI systems.
[![](https://blog.logrocket.com/wp-content/uploads/2021/01/AirBrush_20210107121828-150x150.jpg)](https://blog.logrocket.com/author/emmanueljohn/)[Emmanuel John](https://blog.logrocket.com/author/emmanueljohn/)
Mar 16, 2026 ⋅ 8 min read
[View all posts](https://blog.logrocket.com/)
### Leave a Reply [Cancel reply](https://blog.logrocket.com/ai-shadcn-components\#respond)
Your email address will not be published.Required fields are marked \*
Comment \*
Name \*
Email \*
Website
Save my name, email, and website in this browser for the next time I comment.
Would you be interested in joining LogRocket's developer community?
YeaNo Thanks
Join LogRockets Content Advisory Board. Youll help inform the type of
content we create and get access to exclusive meetups, social accreditation,
and swag.
[Sign up now](https://lp.logrocket.com/blg/content-advisory-board-signup)

File diff suppressed because it is too large Load Diff

View File

@ -1,934 +0,0 @@
[**Advisory boards arent only for executives. Join the LogRocket Content Advisory Board today →**](https://lp.logrocket.com/blg/content-advisory-board-signup)
[![LogRocket blog logo](https://blog.logrocket.com/wp-content/themes/logrocket/assets/logrocket-logo.png)](https://logrocket.com/)
2026-03-02
3006
#react
Amazing Enyichi Agu
192451
116
![](https://blog.logrocket.com/wp-content/uploads/2023/04/logrocket-logo-1.png)
## See how LogRocket's Galileo AI surfaces the most severe issues for you
### No signup required
Check it out
Galileo AI Overview - May 2025
![Video Thumbnail](https://embed-ssl.wistia.com/deliveries/d13588ad6864cb4841845467c9b8feb8.webp?image_crop_resized=1920x1079)
1:15
Click for sound
_**Editors note:** This post was updated in March 2026 by [Elijah Asoula](https://blog.logrocket.com/author/asaoluelijah/) to include Base UI and add updated examples and use cases to make the comparison more actionable._
![Headless UI Alternatives: Radix Primitives, React Aria, Ark UI](https://blog.logrocket.com/wp-content/uploads/2024/06/headless-ui-alternatives-radix-primitives-react-aria-ark-ui.png)
Using React component libraries is a popular way to quickly build React applications. Components from these libraries offer several advantages. First, they follow accessibility guidelines such as [WAI-ARIA](https://www.w3.org/WAI/standards-guidelines/aria/), ensuring that applications are usable by everyone. Second, they come with built-in styling and design so developers can focus on other aspects of their applications. Third, many include pre-defined behaviors — for example, an autocomplete component that filters options based on user input — which saves time and effort compared to building from scratch.
React component libraries are also typically optimized for performance. Because they are maintained by large communities or organizations, they receive regular updates and follow efficient coding practices. Examples include [Material UI](https://blog.logrocket.com/guide-material-design-react/), [Chakra UI](https://blog.logrocket.com/chakra-ui-adoption-guide/), and [React Bootstrap](https://www.youtube.com/watch?v=NlZUtfNVAkI).
However, these libraries leave limited room for customization. You can usually tweak styles, but you cannot fundamentally change the underlying design system. A developer may want the accessibility and functionality benefits of a component library while still implementing a completely custom design system.
Headless (unstyled) component libraries were created to fill this gap. A headless component library provides fully functional components without imposing styling. With headless components, developers are responsible for styling them however they see fit.
Tailwind Labs [Headless UI](https://headlessui.com/) is one of the most popular headless libraries in the React ecosystem. While it works well for many projects, it is not always the best choice for every use case. This article explores several alternatives for unstyled components, including Radix Primitives, React Aria, Ark UI, and Base UI.
### 🚀 Sign up for The Replay newsletter
[**The Replay**](https://blog.logrocket.com/the-replay-archive/) is a weekly newsletter for dev and engineering leaders.
Delivered once a week, it's your curated guide to the most important conversations around frontend dev, emerging AI tools, and the state of modern software.
Fields marked with an \* are required
Email \*
If you are a human seeing this field, please leave it empty.
## Prerequisites
To follow along with this guide, you should have a basic understanding of HTML, CSS, JavaScript, and React.
## Why not just use Tailwind Labs Headless UI library?
Headless UI is an unstyled React component library developed by Tailwind Labs, the creators of Tailwind CSS. The library is designed to integrate particularly well with Tailwind CSS, as noted in its documentation. It is also one of the most widely adopted headless libraries, with around 28K GitHub stars and millions of weekly npm downloads.
However, Headless UI is limited in the number of unstyled components it provides. At the time of writing, it offers 16 primary components. The other libraries covered in this article provide significantly more components for broader use cases. Additionally, some of these alternatives include utility components and helper functions that Headless UI does not offer.
Lets explore these alternatives.
## Radix Primitives
[Radix Primitives](https://www.radix-ui.com/primitives) is a library of unstyled React components built by the team behind [Radix UI](https://radix-ui.com/), a UI library with fully styled and customizable components. According to its website, the Node.js, Vercel, and Supabase teams use Radix Primitives. The project has approximately 18K stars on [GitHub](https://github.com/radix-ui/primitives).
You can [style Radix Primitives components](https://blog.logrocket.com/radix-ui-adoption-guide/#:~:text=you%20should%20know.-,Radix%20Primitives,-Radix%20Primitives%20is) using any styling solution, including CSS, Tailwind CSS, or CSS-in-JS. The components also support server-side rendering. Radix provides comprehensive documentation for each primitive, explaining usage patterns and composition strategies.
### Installing and using Radix Primitives
The following steps demonstrate how to install and use Radix Primitives. In this example, well import a dialog component and style it using vanilla CSS.
First, [create a React project](https://react.dev/learn/creating-a-react-app) using your preferred framework, or open an existing project.
Next, install the Radix primitive you need. Radix publishes each component as a separate package. For this example, install the `Dialog` component:
```bash
npm install @radix-ui/react-dialog
```
Now, create a file to import and customize the unstyled component:
```javascript
// RadixDialog.jsx
import * as Dialog from '@radix-ui/react-dialog';
import './radix.style.css';
function RadixDialog() {
return (
<Dialog.Root>
<Dialog.Trigger className='btn primary-btn'>
Radix Dialog
</Dialog.Trigger>
<Dialog.Portal>
<Dialog.Overlay className='dialog-overlay' />
<Dialog.Content className='dialog-content'>
<Dialog.Title className='dialog-title'>
Confirm Deletion
</Dialog.Title>
<Dialog.Description className='dialog-body'>
Are you sure you want to permanently delete this file?
</Dialog.Description>
<div className='bottom-btns'>
<Dialog.Close className='btn'>Cancel</Dialog.Close>
<Dialog.Close className='btn red-btn'>Delete Forever</Dialog.Close>
</div>
</Dialog.Content>
</Dialog.Portal>
</Dialog.Root>
);
}
export default RadixDialog;
```
Next, add styling:
```css
/* radix.style.css */
.btn {
padding: 0.5rem 1.2rem;
border-radius: 0.2rem;
border: none;
cursor: pointer;
}
.primary-btn {
background-color: #1e64e7;
color: white;
box-shadow: rgba(0, 0, 0, 0.2) 0px 2px 10px;
}
.red-btn {
background-color: #d32f2f;
color: #ffffff;
box-shadow: rgba(0, 0, 0, 0.2) 0px 2px 10px;
}
.dialog-overlay {
background-color: rgba(0, 0, 0, 0.4);
position: fixed;
inset: 0;
animation: overlayAnimation 200ms cubic-bezier(0.19, 1, 0.22, 1);
}
.dialog-content {
background-color: white;
position: fixed;
border-radius: 0.2rem;
top: 50%;
left: 50%;
translate: -50% -50%;
width: 90vw;
max-width: 450px;
padding: 2.5rem;
box-shadow: rgba(50, 50, 93, 0.25) 0px 2px 5px -1px,
rgba(0, 0, 0, 0.3) 0px 1px 3px -1px;
}
.dialog-title {
font-size: 1.1rem;
padding-bottom: 0.5rem;
border-bottom: 3px solid #dfdddd;
margin-bottom: 1rem;
}
.dialog-body {
margin-bottom: 3rem;
}
.bottom-btns {
display: flex;
justify-content: flex-end;
}
.bottom-btns .btn:last-child {
display: inline-block;
margin-left: 1rem;
}
@keyframes overlayAnimation {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
```
Finally, export and render the component in the DOM.
Here is the UI demo of the dialog component we styled above:
![Dialog box built using Radix Primitives styled with custom CSS](https://blog.logrocket.com/wp-content/uploads/2024/06/dialog-box-radix-primitive.gif)Dialog box built using Radix Primitives styled with custom CSS
### Radix Primitives pros and cons
Like every headless library covered in this guide, Radix Primitives has both advantages and tradeoffs.
**Pros**
* * *
[![](https://blog.logrocket.com/wp-content/uploads/2023/07/Screen-Shot-2023-07-06-at-7.43.53-AM.png)\\
\\
**Over 200k developers use LogRocket to create better digital experiences** \\
\\
![](https://blog.logrocket.com/wp-content/uploads/2022/08/rocket-button-icon.png)Learn more →](https://lp.logrocket.com/blg/learn-more)
* * *
- It offers 28 main components, significantly more than Headless UI.
- You can install components individually, allowing incremental adoption.
- It provides an `asChild` prop that lets developers change the default DOM element of a Radix component — a pattern known as [composition](https://www.radix-ui.com/primitives/docs/guides/composition).
**Cons**
- Installing multiple components individually can feel repetitive.
- The anatomy-based structure of components can take time to understand.
## React Aria
[React Aria](https://react-spectrum.adobe.com/react-aria/index.html) is a library of unstyled components released by Adobe as part of its React UI collection, [React Spectrum](https://github.com/adobe/react-spectrum). While Adobe does not maintain a separate repository exclusively for React Aria, the React Spectrum repository has over 14K GitHub stars at the time of writing. Its npm package, `react-aria-components`, receives thousands of weekly downloads.
React Aria allows developers to style components using any preferred styling method. It also supports incremental adoption through [React Aria hooks](https://react-spectrum.adobe.com/react-aria/hooks.html), enabling fine-grained control over component behavior.
### Installing and using React Aria
In this example, well build another dialog box using React Aria, styled similarly to the Radix example.
First, create a new React application or open an existing project. Then install the component package:
```bash
npm install react-aria-components
```
Next, import the required components to construct a dialog:
```javascript
// AriaDialog.jsx
import {
Button,
Dialog,
DialogTrigger,
Heading,
Modal,
ModalOverlay
} from 'react-aria-components';
import './aria.style.css';
function AriaDialog() {
return (
<DialogTrigger>
<Button className='btn primary-btn'>
React Aria Dialog
</Button>
<ModalOverlay isDismissable>
<Modal>
<Dialog>
{({ close }) => (
<>
<Heading slot='title'>
Confirm Deletion
</Heading>
<p className='dialog-body'>
Are you sure you want to permanently delete this file?
</p>
<div className='bottom-btns'>
<Button className='btn' onPress={close}>
Cancel
</Button>
<Button className='btn red-btn' onPress={close}>
Delete Forever
</Button>
</div>
</>
)}
</Dialog>
</Modal>
</ModalOverlay>
</DialogTrigger>
);
}
export default AriaDialog;
```
Now, add styling. React Aria provides built-in class names such as `.react-aria-Button`, which you can use directly in CSS. You can also override them with custom classes like `.btn` in this example:
```css
/* aria.style.css */
.btn {
padding: 0.5rem 1.2rem;
border-radius: 0.2rem;
border: none;
cursor: pointer;
}
.primary-btn {
background-color: #1e64e7;
color: white;
box-shadow: rgba(0, 0, 0, 0.2) 0px 2px 10px;
}
.red-btn {
background-color: #d32f2f;
color: #ffffff;
box-shadow: rgba(0, 0, 0, 0.2) 0px 2px 10px;
}
.react-aria-ModalOverlay {
background-color: rgba(0, 0, 0, 0.4);
position: fixed;
inset: 0;
animation: overlayAnimation 200ms cubic-bezier(0.19, 1, 0.22, 1);
display: flex;
justify-content: center;
align-items: center;
}
.react-aria-Dialog {
background-color: white;
border-radius: 0.2rem;
width: 90vw;
max-width: 450px;
padding: 2.5rem;
box-shadow: rgba(50, 50, 93, 0.25) 0px 2px 5px -1px,
rgba(0, 0, 0, 0.3) 0px 1px 3px -1px;
outline: none;
}
.react-aria-Dialog .react-aria-Heading {
font-size: 1.1rem;
padding-bottom: 0.5rem;
border-bottom: 3px solid #dfdddd;
margin-bottom: 1rem;
}
.dialog-body {
margin-bottom: 3rem;
}
.bottom-btns {
display: flex;
justify-content: flex-end;
}
.bottom-btns .btn:last-child {
display: inline-block;
margin-left: 1rem;
}
@keyframes overlayAnimation {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
```
Finally, export and render the component in the DOM.
Here is the output of the dialog box in this example:
![Dialog component built using React Aria styled with custom CSS](https://blog.logrocket.com/wp-content/uploads/2024/06/dialog-box-react-aria.gif)Dialog component built using React Aria styled with custom CSS
### React Aria pros and cons
**Pros**
- It offers hooks for individual components, which support incremental adoption.
- It provides 43 main components.
- All components include built-in class names, simplifying styling.
**Cons**
- Some components require more setup. For example, the dialog required destructuring the `close` function and explicitly wiring it to buttons.
- Components often need to be combined to function fully. In this example, we used `Button`, `Dialog`, `DialogTrigger`, `Heading`, `Modal`, and `ModalOverlay` together to build a dialog. This structure can feel complex at first.
## Ark UI
[Ark UI](https://ark-ui.com/) is a library of unstyled components that work across React, Vue, and Solid. It is developed by Chakra Systems, the team behind Chakra UI. The project has gained steady adoption, with around 4.9K stars on [GitHub](https://github.com/chakra-ui/ark) and thousands of weekly npm downloads.
Like Radix Primitives and React Aria, Ark UI allows you to style headless components using any method you prefer, including CSS, Tailwind CSS, Panda CSS, or Styled Components. One of its distinguishing features is multi-framework support.
### Installing and using Ark UI
In this example, well build another dialog box using Ark UI and style it with vanilla CSS.
First, create a new React project or open an existing one. Then install Ark UI for React:
```bash
npm install @ark-ui/react
```
Next, import and use the unstyled components. Below is the anatomy of a dialog in Ark UI:
```javascript
// ArkDialog.jsx
import { Dialog, Portal } from '@ark-ui/react';
import './ark.style.css';
function ArkDialog() {
return (
<Dialog.Root>
<Dialog.Trigger className='btn primary-btn'>
Ark UI Dialog
</Dialog.Trigger>
<Portal>
<Dialog.Backdrop />
<Dialog.Positioner>
<Dialog.Content>
<Dialog.Title>
Confirm Deletion
</Dialog.Title>
<Dialog.Description>
Are you sure you want to permanently delete this file?
</Dialog.Description>
<div className='bottom-btns'>
<Dialog.CloseTrigger className='btn'>
Cancel
</Dialog.CloseTrigger>
<Dialog.CloseTrigger className='btn red-btn'>
Delete Forever
</Dialog.CloseTrigger>
</div>
</Dialog.Content>
</Dialog.Positioner>
</Portal>
</Dialog.Root>
);
}
export default ArkDialog;
```
Now, style the component using your preferred method. Here is a vanilla CSS example:
```css
/* ark.style.css */
.btn {
padding: 0.5rem 1.2rem;
border-radius: 0.2rem;
border: none;
cursor: pointer;
}
.primary-btn {
background-color: #1e64e7;
color: white;
box-shadow: rgba(0, 0, 0, 0.2) 0px 2px 10px;
}
.red-btn {
background-color: #d32f2f;
color: #ffffff;
box-shadow: rgba(0, 0, 0, 0.2) 0px 2px 10px;
}
[data-scope="dialog"][data-part="backdrop"] {
background-color: rgba(0, 0, 0, 0.4);
position: fixed;
inset: 0;
animation: backdropAnimation 200ms cubic-bezier(0.19, 1, 0.22, 1);
}
[data-scope="dialog"][data-part="positioner"] {
position: fixed;
top: 50%;
left: 50%;
translate: -50% -50%;
width: 90vw;
max-width: 450px;
}
[data-scope="dialog"][data-part="content"] {
background-color: white;
padding: 2.5rem;
border-radius: 0.2rem;
box-shadow: rgba(50, 50, 93, 0.25) 0px 2px 5px -1px,
rgba(0, 0, 0, 0.3) 0px 1px 3px -1px;
}
[data-scope="dialog"][data-part="title"] {
font-size: 1.1rem;
padding-bottom: 0.5rem;
border-bottom: 3px solid #dfdddd;
margin-bottom: 1rem;
}
[data-scope="dialog"][data-part="description"] {
margin-bottom: 3rem;
}
.bottom-btns {
display: flex;
justify-content: flex-end;
}
.bottom-btns .btn:last-child {
display: inline-block;
margin-left: 1rem;
}
@keyframes backdropAnimation {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
```
Finally, export and render the component. Below is the output of the example:
![Dialog component built using Ark UI styled with custom CSS](https://blog.logrocket.com/wp-content/uploads/2024/06/dialog-box-ark-ui.gif)Dialog component built using Ark UI styled with custom CSS
### Ark UI pros and cons
**Pros**
- It provides 34 main components.
- It includes advanced components such as a carousel and circular progress bar, which can be complex to implement from scratch.
- It supports [component composition](https://ark-ui.com/react/docs/guides/composition) using the `asChild` prop, similar to Radix Primitives.
**Cons**
- It does not provide built-in class names like React Aria.
- The recommended styling approach relies on `data-scope` and `data-part` attributes, which may feel unfamiliar at first.
For example, styling a specific part of the dialog can look like this:
```css
[data-scope="dialog"][data-part="positioner"] {
position: fixed;
top: 50%;
left: 50%;
translate: -50% -50%;
width: 90vw;
max-width: 450px;
}
```
Developers who prefer a more familiar workflow can assign custom class names using `className` and target those instead:
```css
.primary-btn {
background-color: #1e64e7;
color: white;
box-shadow: rgba(0, 0, 0, 0.2) 0px 2px 10px;
}
```
This approach preserves Ark UIs headless behavior while allowing conventional CSS styling.
## Base UI
[Base UI](https://base-ui.com/) is a library of unstyled React components built by contributors from Radix, Floating UI, and the Material UI team. While it follows the same headless philosophy as the other libraries discussed in this article, Base UI places a stronger emphasis on stable APIs that are well-suited for building long-term custom design systems. At the time of writing, Base UI has more than 8.1K stars on its [GitHub repository](https://github.com/mui/base-ui) and is actively maintained with regular releases.
Like the other headless libraries in this guide, Base UI components can be styled using CSS, Tailwind CSS, or CSS-in-JS. The documentation also includes guidance on advanced patterns such as controlled dialogs and detached triggers.
### Installing and using Base UI
Unlike Radix Primitives, which publishes each component separately, Base UI ships all components in a single tree-shakable package. This makes installation straightforward.
First, create a new React project or open an existing one. Then install Base UI:
```bash
npm i @base-ui/react
```
Next, create a file and import the `Dialog` component. In this example, well build another dialog box:
```javascript
// BaseDialog.jsx
import { Dialog } from '@base-ui/react/dialog';
import './base.style.css';
function BaseDialog() {
return (
<Dialog.Root>
<Dialog.Trigger className='btn primary-btn'>
Base UI Dialog
</Dialog.Trigger>
<Dialog.Portal>
<Dialog.Backdrop className='dialog-overlay' />
<Dialog.Popup className='dialog-content'>
<Dialog.Title className='dialog-title'>
Confirm Deletion
</Dialog.Title>
<Dialog.Description className='dialog-body'>
Are you sure you want to permanently delete this file?
</Dialog.Description>
<div className='bottom-btns'>
<Dialog.Close className='btn'>
Cancel
</Dialog.Close>
<Dialog.Close className='btn red-btn'>
Delete Forever
</Dialog.Close>
</div>
</Dialog.Popup>
</Dialog.Portal>
</Dialog.Root>
);
}
export default BaseDialog;
```
Now, add styling:
```css
/* base.style.css */
.btn {
padding: 0.5rem 1.2rem;
border-radius: 0.2rem;
border: none;
cursor: pointer;
}
.primary-btn {
background-color: #1e64e7;
color: white;
box-shadow: rgba(0, 0, 0, 0.2) 0px 2px 10px;
}
.red-btn {
background-color: #d32f2f;
color: #ffffff;
box-shadow: rgba(0, 0, 0, 0.2) 0px 2px 10px;
}
.dialog-overlay {
background-color: rgba(0, 0, 0, 0.4);
position: fixed;
inset: 0;
animation: overlayAnimation 200ms cubic-bezier(0.19, 1, 0.22, 1);
}
.dialog-content {
background-color: white;
position: fixed;
border-radius: 0.2rem;
top: 50%;
left: 50%;
translate: -50% -50%;
width: 90vw;
max-width: 450px;
padding: 2.5rem;
box-shadow: rgba(50, 50, 93, 0.25) 0px 2px 5px -1px,
rgba(0, 0, 0, 0.3) 0px 1px 3px -1px;
}
.dialog-title {
font-size: 1.1rem;
padding-bottom: 0.5rem;
border-bottom: 3px solid #dfdddd;
margin-bottom: 1rem;
}
.dialog-body {
margin-bottom: 3rem;
}
.bottom-btns {
display: flex;
justify-content: flex-end;
}
.bottom-btns .btn:last-child {
display: inline-block;
margin-left: 1rem;
}
@keyframes overlayAnimation {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
```
Finally, import and render the component in your application:
* * *
* * *
```javascript
import './App.css';
import BaseDialog from './BaseDialog';
function App() {
return (
<>
<BaseDialog />
</>
);
}
export default App;
```
And you should see output similar to the example below:
![Dialog component built using Base UI styled with custom CSS](https://paper-attachments.dropboxusercontent.com/s_08FF8C07E650BFE785F4BD3E05EABB4D3111B626CF5B80B239A8E948CFAEF6ED_1768982503267_Jan-21-2026+08-51-42.gif)Dialog component built using Base UI styled with custom CSS
### Base UI pros and cons
**Pros**
- It ships as a single tree-shakable package, eliminating the need to install components individually.
- It includes strong documentation and supports advanced patterns such as controlled dialogs and detached triggers.
**Cons**
- Its ecosystem is still growing compared to more established alternatives.
- Because it is unstyled by design, significant styling work is still required to align it with a production design system.
## Comparing the headless component libraries
To provide a clearer overview of how these headless UI libraries compare across API design, styling flexibility, composition model, and intended use cases, the table below highlights the key differences between Radix Primitives, React Aria, Ark UI, and Base UI.
| Dimension | Radix Primitives | React Aria | Ark UI | Base UI |
| --- | --- | --- | --- | --- |
| **Primary goal** | Polished primitives for app UIs | Accessibility-first primitives | Cross-framework state-driven primitives | Foundation for custom design systems |
| **Mental model** | Component anatomy and composition | Hooks with explicit state | State machines and parts | Low-level primitives meant to be wrapped |
| **Typical usage** | Used directly in application code | Composed per component | Assembled from parts | Extended into internal components |
| **Styling approach** | `className`, `asChild` | Built-in classes with overrides | `data-part` / `data-scope` with `className` | `className` and wrapper components |
| **Ease of styling** | Easy and familiar | Easy once conventions are understood | Moderate, unconventional at first | Easy, but assumes design ownership |
| **Composition flexibility** | High | Very high | High | Very high |
| **Accessibility transparency** | Mostly abstracted | Very explicit | Abstracted via state | Abstracted but predictable |
| **Learning curve** | Moderate | Steep | Moderate to steep | Moderate |
| **Best suited for** | Product teams building applications | Accessibility-critical applications | Multi-framework design systems | Teams building custom design systems |
| **Framework support** | React | React | React, Vue, Solid | React |
This comparison demonstrates that while these libraries often provide similar component coverage, they differ significantly in how components are composed, styled, and extended.
Choosing the right headless UI library ultimately depends on your project goals, team preferences, and long-term maintenance strategy. The following quick guide can help narrow down your options:
- **Use Radix Primitives** if you want mature, well-documented components that can be used directly in application code with minimal setup.
- **Use React Aria** if accessibility is a primary concern and you prefer explicit, hook-based control over component behavior.
- **Use Ark UI** if you need headless components that work across multiple frameworks such as React, Vue, and Solid.
- **Use Base UI** if you are building a custom design system and want a flexible, long-term foundation for your own components.
The best choice depends less on feature parity and more on how well a librarys design philosophy aligns with your teams workflow and architectural goals.
## Conclusion
This guide explored why developers may look beyond Tailwind Labs Headless UI library when choosing unstyled component libraries. We examined several strong alternatives, including Radix Primitives, React Aria, Ark UI, and Base UI.
The frontend ecosystem continues to adopt headless UI libraries because many teams want more control over how components behave and how they are styled. Having multiple headless options available is beneficial, as different projects have different architectural and design needs.
## Get set up with LogRocket's modern React error tracking in minutes:
1. Visit [https://logrocket.com/signup/](https://lp.logrocket.com/blg/react-signup-general) to get
an app ID
2. Install LogRocket via npm or script tag. `LogRocket.init()` must be called client-side, not
server-side
- [npm](https://blog.logrocket.com/headless-ui-alternatives/#plug-tab-1)
- [Script tag](https://blog.logrocket.com/headless-ui-alternatives/#plug-tab-2)
```
$ npm i --save logrocket
// Code:
import LogRocket from 'logrocket';
LogRocket.init('app/id');
```
```
// Add to your HTML:
<script src="https://cdn.lr-ingest.com/LogRocket.min.js"></script>
<script>window.LogRocket && window.LogRocket.init('app/id');</script>
```
3. (Optional) Install plugins for deeper integrations with your stack:
- Redux middleware
- NgRx middleware
- Vuex plugin
[Get started now](https://lp.logrocket.com/blg/react-signup-general)
- [#react](https://blog.logrocket.com/tag/react/)
![](https://blog.logrocket.com/wp-content/uploads/2022/06/footer-cta-dots-left.png)![](https://blog.logrocket.com/wp-content/uploads/2022/06/footer-cta-dots-right.png)
![](https://blog.logrocket.com/wp-content/uploads/2022/09/logrocket-logo-frontend-analytics.png)
## Stop guessing about your digital experience with LogRocket
[Get started for free](https://lp.logrocket.com/blg/signup)
#### Recent posts:
[![Crud Rest Api With Node Js Express And Postgresql](https://blog.logrocket.com/wp-content/uploads/2022/05/CRUD-REST-API-Node-js-Express-PostgreSQL.png)\\
**CRUD REST API with Node.js, Express, and PostgreSQL**](https://blog.logrocket.com/crud-rest-api-node-js-express-postgresql/)
Build a CRUD REST API with Node.js, Express, and PostgreSQL, then modernize it with ES modules, async/await, built-in Express middleware, and safer config handling.
[![](https://blog.logrocket.com/wp-content/uploads/2019/04/taniarascia-150x150.png)](https://blog.logrocket.com/author/taniarascia/)[Tania Rascia](https://blog.logrocket.com/author/taniarascia/)
Mar 25, 2026 ⋅ 16 min read
[![the replay march 25](https://blog.logrocket.com/wp-content/uploads/2026/03/The-Replay-Graphic-21.png)\\
**The Replay (3/25/26): Senior dev hiring woes, what AI agents miss, and more**](https://blog.logrocket.com/the-replay-3-25-26/)
Discover whats new in The Replay, LogRockets newsletter for dev and engineering leaders, in the March 25th issue.
[![](https://secure.gravatar.com/avatar/bdb8fc1c58c8a14e8c52f80c0ff964372237e773db8d0d1a179151099f28c358?s=50&d=mm&r=g)](https://blog.logrocket.com/author/matthewmaccormack/)[Matt MacCormack](https://blog.logrocket.com/author/matthewmaccormack/)
Mar 25, 2026 ⋅ 29 sec read
[![emmanuel john senior dev hiring](https://blog.logrocket.com/wp-content/uploads/2026/03/3.25-Emmanuel-John-Senior-Engineers-1.png)\\
**The hidden skills gap in senior dev hiring (and how to screen for it)**](https://blog.logrocket.com/the-hidden-skills-gap-in-senior-dev-hiring/)
Discover a practical framework for redesigning your senior developer hiring process to screen for real diagnostic skill.
[![](https://blog.logrocket.com/wp-content/uploads/2021/01/AirBrush_20210107121828-150x150.jpg)](https://blog.logrocket.com/author/emmanueljohn/)[Emmanuel John](https://blog.logrocket.com/author/emmanueljohn/)
Mar 25, 2026 ⋅ 12 min read
[![](https://blog.logrocket.com/wp-content/uploads/2026/03/Does-the-Speculation-Rules-API-boost-web-speed-I-tested-it.png)\\
**Does the Speculation Rules API boost web speed? I tested it**](https://blog.logrocket.com/speculation-rules-api-web-speed-test/)
I tested the Speculation Rules API in a real project to see if it actually improves navigation speed. Heres what worked, what didnt, and where its worth using.
[![](https://blog.logrocket.com/wp-content/uploads/2025/01/IMG_1212-150x150.jpg)](https://blog.logrocket.com/author/judemiracle/)[Jude Miracle](https://blog.logrocket.com/author/judemiracle/)
Mar 24, 2026 ⋅ 10 min read
[View all posts](https://blog.logrocket.com/)
#### 2 Replies to "Headless UI alternatives: Radix Primitives vs. React Aria vs. Ark UI vs. Base UI"
1. \> such as Base UI (from the Material UI team)
This is confusing. The link points to “MUI Base”. But this project has a successor now: Base UI, [https://base-ui.com/](https://base-ui.com/).
[Reply](https://blog.logrocket.com/headless-ui-alternatives/#comment-51239)
1. Thanks for noticing this. Should be all set now
[Reply](https://blog.logrocket.com/headless-ui-alternatives/#comment-51257)
### Leave a Reply [Cancel reply](https://blog.logrocket.com/headless-ui-alternatives/\#respond)
Your email address will not be published.Required fields are marked \*
Comment \*
Name \*
Email \*
Website
Save my name, email, and website in this browser for the next time I comment.
Hey there, want to help make our blog better?
YeaNo Thanks
Join LogRockets Content Advisory Board. Youll help inform the type of
content we create and get access to exclusive meetups, social accreditation,
and swag.
[Sign up now](https://lp.logrocket.com/blg/content-advisory-board-signup)

View File

@ -1,130 +0,0 @@
# Overview
Key building blocks of the OpenUI framework and the built-in component libraries.
Copy MarkdownOpen
OpenUI is built around four core building blocks that work together to turn LLM output into rendered UI:
- **Library** — A collection of components defined with Zod schemas and React renderers. The library is the contract between your app and the AI — it defines what components the LLM can use and how they render.
- **Prompt Generator** — Converts your library into a system prompt that instructs the LLM to output valid OpenUI Lang. Includes syntax rules, component signatures, streaming guidelines, and your custom examples/rules.
- **Parser** — Parses OpenUI Lang text (line-by-line, streaming-compatible) into a typed element tree. Validates against your library's JSON Schema and gracefully handles partial/invalid output.
- **Renderer** — The `<Renderer />` React component takes parsed output and maps each element to your library's React components, rendering the UI progressively as the stream arrives.
## [Built-in Component Libraries](https://www.openui.com/docs/openui-lang/overview\#built-in-component-libraries)
OpenUI ships with two ready-to-use libraries via `@openuidev/react-ui`. Both include layouts, content blocks, charts, forms, tables, and more.
### [General-purpose library (`openuiLibrary`)](https://www.openui.com/docs/openui-lang/overview\#general-purpose-library-openuilibrary)
Root component is `Stack`. Includes the full component suite with flexible layout primitives. Use this for standalone rendering, playgrounds, and non-chat interfaces.
```
import { openuiLibrary, openuiPromptOptions } from "@openuidev/react-ui/genui-lib";
import { Renderer } from "@openuidev/react-lang";
// Generate system prompt
const systemPrompt = openuiLibrary.prompt(openuiPromptOptions);
// Render streamed output
<Renderer library={openuiLibrary} response={streamedText} isStreaming={isStreaming} />
```
### [Chat-optimized library (`openuiChatLibrary`)](https://www.openui.com/docs/openui-lang/overview\#chat-optimized-library-openuichatlibrary)
Root component is `Card` (vertical container, no layout params). Adds chat-specific components like `FollowUpBlock`, `ListBlock`, and `SectionBlock`. Does not include `Stack` — responses are always single-card, vertically stacked.
```
import { openuiChatLibrary, openuiChatPromptOptions } from "@openuidev/react-ui/genui-lib";
import { FullScreen } from "@openuidev/react-ui";
// Use with a chat layout
<FullScreen
componentLibrary={openuiChatLibrary}
processMessage={...}
streamProtocol={openAIAdapter()}
/>
```
Both libraries expose a `.prompt()` method to generate the system prompt your LLM needs. See [System Prompts](https://www.openui.com/docs/openui-lang/system-prompts) for CLI and programmatic generation options.
### [Extend a built-in library](https://www.openui.com/docs/openui-lang/overview\#extend-a-built-in-library)
```
import { createLibrary, defineComponent } from "@openuidev/react-lang";
import { openuiLibrary } from "@openuidev/react-ui/genui-lib";
import { z } from "zod";
const ProductCard = defineComponent({
name: "ProductCard",
description: "Product tile",
props: z.object({
name: z.string(),
price: z.number(),
}),
component: ({ props }) => <div>{props.name}: ${props.price}</div>,
});
const myLibrary = createLibrary({
root: openuiLibrary.root ?? "Stack",
componentGroups: openuiLibrary.componentGroups,
components: [...Object.values(openuiLibrary.components), ProductCard],
});
```
## [Usage Example](https://www.openui.com/docs/openui-lang/overview\#usage-example)
Define LibRender CodeSystem PromptLLM Output
OpenUI Lang (Token Efficient)Copy
```
root = Stack([welcomeCard])
welcomeCard = MyCard([welcomeHeader, welcomeBody])
welcomeHeader = CardHeader("Welcome", "Get started with our platform")
welcomeBody = Stack([signupForm], "column", "m")
signupForm = Form("signup", [nameField, emailField], actions)
nameField = FormControl("Name", Input("name", "Your name", "text", ["required", "minLength:2"]))
emailField = FormControl("Email", Input("email", "you@example.com", "email", ["required", "email"]))
actions = Buttons([signUpBtn, learnMoreBtn], "row")
signUpBtn = Button("Sign up", "submit:signup", "primary")
learnMoreBtn = Button("Learn more", "action:learn_more", "secondary")
```
Output Preview
Welcome
Get started with our platform
Name\*
Email\*
Sign upLearn more
## [Next Steps](https://www.openui.com/docs/openui-lang/overview\#next-steps)
[**Defining Components** \\
\\
Create custom components with Zod schemas and React renderers.](https://www.openui.com/docs/openui-lang/defining-components) [**System Prompts** \\
\\
Generate and customize LLM instructions from your library.](https://www.openui.com/docs/openui-lang/system-prompts) [**The Renderer** \\
\\
Parse and render streamed OpenUI Lang in React.](https://www.openui.com/docs/openui-lang/renderer) [**Chat Integration** \\
\\
Build AI chat interfaces with prebuilt layouts.](https://www.openui.com/docs/chat)
[Quick Start\\
\\
Bootstrap a Generative UI chat app in under a minute.](https://www.openui.com/docs/openui-lang/quickstart) [Defining Components\\
\\
Define OpenUI components with Zod and React renderers.](https://www.openui.com/docs/openui-lang/defining-components)
### On this page
[Built-in Component Libraries](https://www.openui.com/docs/openui-lang/overview#built-in-component-libraries) [General-purpose library (`openuiLibrary`)](https://www.openui.com/docs/openui-lang/overview#general-purpose-library-openuilibrary) [Chat-optimized library (`openuiChatLibrary`)](https://www.openui.com/docs/openui-lang/overview#chat-optimized-library-openuichatlibrary) [Extend a built-in library](https://www.openui.com/docs/openui-lang/overview#extend-a-built-in-library) [Usage Example](https://www.openui.com/docs/openui-lang/overview#usage-example) [Next Steps](https://www.openui.com/docs/openui-lang/overview#next-steps)

View File

@ -1,289 +0,0 @@
# OpenUI Specification
draft-01
## 1\. Introduction
**OpenUI** is a specification format for defining User Interface (UI) components in an abstract, implementation-agnostic manner. Inspired by specifications like **OpenAPI**, OpenUI describes UI components, their properties and behaviors in a way that can be used across various UI libraries and frameworks. This approach provides a standardized, machine-readable and human-readable model for UI libraries, facilitating a consistent means of documentation, testing and code generation.
OpenUI is designed to be **AI-native**, making it easier for AI tools and assistants to parse, understand and leverage information about your UI components or design system. By offering a uniform description of components, OpenUI aids in bridging the gap between diverse frameworks, ensuring interoperability and reducing fragmentation.
## 2\. Specification Interpretation (RFC 2119)
This specification uses key terms from [RFC 2119](https://www.rfc-editor.org/rfc/rfc2119):
- **MUST** indicates an absolute requirement.
- **SHOULD** indicates a recommendation that, while not strictly required, carries significant weight if not implemented.
- **MAY** indicates an option or possibility left to implementer discretion.
These terms are used carefully within this document to clarify how certain aspects of OpenUI **MUST**, **SHOULD**, or **MAY** be interpreted or implemented.
## 3\. Purpose and Goals
OpenUI addresses the growing need for a common language to describe UI components. It streamlines collaboration among designers, developers, technical writers and AI-based systems. Its key goals are:
1. **Standardizing UI Component Libraries**
Provide a consistent schema for describing components and design systems.
2. **Interoperability Across Frameworks**
Abstract away implementation details, enabling easier migration or shared usage across React, Vue, Angular and other libraries.
3. **AI-Native**
Supply structured data optimized for consumption by AI tools, improving automated documentation, validation and code generation.
4. **Efficiency**
Reduce duplication and confusion by having a single, authoritative reference that clarifies how components behave and interact.
5. **Built-In Accessibility and Validation**
Promote best practices for accessible design while enabling validation and testing tools to verify component conformance.
6. **Support for Web and Native Platforms**
Offer a universal approach that can adapt to both web-based and native environments.
7. **Documentation + Testing + Code Generation**
Enable a broad ecosystem of tooling, from auto-generated docs to integrated testing frameworks.
## 4\. Overview and Key Features
### AI-Native Specification
OpenUI's structured format is particularly well-suited for AI-based analysis and tooling. By adhering to a consistent schema, developers can integrate OpenUI with LLM-based assistants or other AI tools to:
- Generate automated tests and documentation.
- Provide contextual suggestions during development.
- Perform higher-level reasoning about component usage.
### Standardization of UI Components
OpenUI makes it straightforward to define each component's shape, props, events and usage guidelines. This standardization can reduce the learning curve for new developers and makes it easier to share components across teams or projects.
### Efficiency and Universal Application
By referencing a single specification file, organizations can ensure consistent APIs and behaviors across multiple implementations. OpenUI also supports various popular UI libraries and frameworks out of the box.
## 5\. Similarities and Differences with Other Tools
1. **Type Definition Files**
- **Similarities**: Both OpenUI and typical type definition files (e.g., TypeScript `.d.ts`) can define types, enumerations and interfaces in a way that tools can parse.
- **Differences**:
- Type definitions often focus on compile-time checks and may include internal or unrelated types across multiple files, without extensive usage or behavioral documentation.
- OpenUI, by contrast, is designed to be more concise and self-contained. It places all relevant component information (including usage examples, accessibility considerations and enumerated props) into a single, portable file. This provides a broader but more focused view of each components intended purpose and functionality.
2. `**llms.txt**`
- **Similarities**: Can also include structured text or partial metadata for Large Language Models (LLMs).
- **Differences**: `llms.txt` often contains entire documentation sets or large text blocks, making them unwieldy for direct AI consumption without retrieval augmented generation (RAG). OpenUI, by contrast, is deliberately minimal and structured specifically for UI component definitions, easing both human and machine comprehension.
3. **Storybook**
- **Similarities**: Provides extensive documentation for UI components and their usage patterns.
- **Differences**: Storybook offers live previews and interactive exploration, while OpenUI is a static, implementation-agnostic specification.
4. **OpenAPI**
- **Similarities**: Both define a domain (OpenAPI for HTTP APIs, OpenUI for UI components) using a schema that is machine-readable and human-readable.
- **Differences**: OpenAPI is dedicated to endpoints and data payloads, whereas OpenUI focuses on user interface components and their properties.
OpenUI **complements** these existing tools by providing a formal specification for UI components, bridging the gap between full interactive environments (e.g., Storybook) and purely type-focused definitions (e.g., TypeScript `.d.ts` files).
## 6\. Why OpenUI?
1. **Consistency**
Streamlines how UI components are documented, reducing confusion among teams and projects.
2. **Framework Agnosticism**
Fits multiple frameworks (e.g., React, Vue, Angular) by focusing on essential component definitions rather than implementation details.
3. **Standardization**
Encourages best practices for design systems and shared UI libraries.
4. **Enhanced Developer Experience**
Simplifies onboarding and collaboration, as all information about a component is located in a centralized specification.
5. **AI Readiness**
Supplies metadata in a format that AI tools can easily parse, leading to improved automated code insights, error detection and overall development efficiency.
## 7\. Specification Format
OpenUI specifications **MUST** be defined in **YAML** or **JSON**. Below is the general structure required at the top level:
| Property | Type | Description |
| --- | --- | --- |
| **name** | string | Name of the UI library or component set. |
| **version** | string | Version of the UI library or component set. |
| **description** | string | Brief overview of the UI library or component set. |
| **components** | object | Collection of components in the library. |
Each entry in `components` represents a **component**, defined by:
| Property | Type | Description |
| --- | --- | --- |
| **package** | string (opt.) | (Optional) Package or module where the component is located. |
| **description** | string | Component overview. |
| **example** | string (opt.) | (Optional) Example usage of the component. |
| **props** | object | Map of properties supported by the component. |
Within `props`, each **prop** can be a string indicating the type or an object containing:
| Property | Type | Description |
| --- | --- | --- |
| **type** | string | Data type for the prop (e.g., `string`, `boolean`, `number`). |
| **description** | string | Explanation of the prop's purpose and usage. |
| **default** | any | Default value for the prop, if applicable. |
| **enum** | array | List of allowable values (if the prop is an enumerated type). |
| **required** | boolean | Whether the prop is mandatory (e.g., `true` means it MUST be provided). |
## 8\. Filename and Discovery
The OpenUI specification file **MUST** be named **`openui.yaml`** or **`openui.json`**. It **SHOULD** reside at the **root** of your GitHub repository or the base path of your website. This approach makes it straightforward for both humans and automated tools to locate and parse the specification.
## 9\. Example Specification
```yaml
name: Example UI Library
version: 1.0.0
description: A sample UI library specification
components:
Button:
description: A clickable button element
example: |
<Button variant="primary" disabled={false}>
Click Me
</Button>
props:
variant:
type: string
description: The visual style of the button.
enum:
- primary
- secondary
- outline
size:
type: string
description: The size of the button.
enum:
- small
- medium
- large
disabled:
type: boolean
description: Disables the button if set to true.
default: false
```
In this example:
- **`variant`** is a string-based enumerated prop.
- **`size`** is another enumerated prop.
- **`disabled`** is a boolean with a default value.
## 10\. Best Practices
1. **Reflect the Design System Accurately**
All components, props and enumerations **SHOULD** accurately match the functionality and constraints in the actual UI library or design system.
2. **Document Events**
If a component emits events (e.g., `onClick`), these **SHOULD** be included to ensure completeness.
3. **Emphasize Accessibility**
Integrate relevant accessibility attributes or guidelines within your component definitions to promote inclusive design.
4. **Maintain Consistency**
Use a uniform style for naming and describing components and props across the specification.
5. **Keep the Specification Updated**
Update the specification whenever the codebase evolves to avoid discrepancies.
## 11\. Future Plans
OpenUI is an evolving specification. Planned or potential enhancements include:
1. **Describing Component State and Lifecycle**
Support for advanced UI patterns, including transitions and complex state management.
2. **Tooling for Code Generation**
Official plugins or libraries to automatically generate documentation, tests and skeleton code.
3. **Library and Framework Integrations**
Core support for popular frameworks such as React, Vue, Angular and others.
4. **Complex UI Patterns**
Handling dynamic scenarios such as multi-step forms, modals, or asynchronous data loading.
## 12\. Contributing
OpenUI is an open-source project and contributions are welcomed. If you wish to propose changes or add new features:
1. **Fork the Repository**
Create a personal fork of the OpenUI repository.
2. **Create a Branch**
Use a descriptive name (e.g., `feature/add-modal-spec`).
3. **Implement Changes**
Follow the established style and schema guidelines when modifying or adding files.
4. **Validate**
Confirm your updates are syntactically correct and conform to the schema requirements.
5. **Submit a Pull Request**
Propose your changes for review by the maintainers.
For more information, refer to the [OpenUI repository](https://github.com/ctate/openui).
## 13\. License
The OpenUI specification is distributed under the **MIT License**

View File

@ -1,361 +0,0 @@
[Skip to main content](https://www.pkgpulse.com/blog/tsup-vs-tsdown-vs-unbuild-typescript-library-bundling-2026#main-content)
## [TL;DR](https://www.pkgpulse.com/blog/tsup-vs-tsdown-vs-unbuild-typescript-library-bundling-2026\#tldr)
**tsup** is the most popular TypeScript library bundler — zero-config, generates ESM + CJS + `.d.ts` types automatically, used by thousands of npm packages. **tsdown** is the next-generation successor to tsup — built on Rolldown (Vite's Rust-based bundler), significantly faster, same DX but better performance. **unbuild** is from the UnJS ecosystem — supports multiple build presets, stub mode for development (no build step), and is used by Nuxt, Nitro, and UnJS libraries internally. In 2026: tsup is the safe choice with the largest community, tsdown is the emerging performance leader, unbuild if you're in the Nuxt/UnJS ecosystem.
## [Key Takeaways](https://www.pkgpulse.com/blog/tsup-vs-tsdown-vs-unbuild-typescript-library-bundling-2026\#key-takeaways)
- **tsup**: ~6M weekly downloads — esbuild-based, zero-config, ESM + CJS + types in one command
- **tsdown**: ~500K weekly downloads — Rolldown-based (Rust), 3-5x faster than tsup, tsup-compatible API
- **unbuild**: ~3M weekly downloads — UnJS, stub mode, multiple presets, Rollup-based
- All three generate dual ESM/CJS output — required for modern npm packages
- All three generate TypeScript declaration files (`.d.ts`) automatically
- tsdown is rapidly gaining adoption in 2026 as the fast tsup replacement
* * *
## [Why Library Bundling Matters](https://www.pkgpulse.com/blog/tsup-vs-tsdown-vs-unbuild-typescript-library-bundling-2026\#why-library-bundling-matters)
```
Problem: publishing TypeScript to npm requires:
1. Compile TypeScript → JavaScript
2. Generate .d.ts type declarations
3. Create both ESM (import) and CJS (require) versions
4. Tree-shaking to minimize bundle size
5. Correct package.json "exports" map
Without a bundler:
tsc --outDir dist → CJS only, no bundling
+ manually maintain package.json exports
+ separately configure dts generation
+ no tree-shaking
With tsup/tsdown/unbuild:
One command → dist/index.mjs + dist/index.cjs + dist/index.d.ts
```
* * *
## [tsup](https://www.pkgpulse.com/blog/tsup-vs-tsdown-vs-unbuild-typescript-library-bundling-2026\#tsup)
[tsup](https://tsup.egoist.dev/) — zero-config library bundler:
### [Setup](https://www.pkgpulse.com/blog/tsup-vs-tsdown-vs-unbuild-typescript-library-bundling-2026\#setup)
```bash
npm install -D tsup typescript
# Add to package.json scripts:
{
"scripts": {
"build": "tsup",
"dev": "tsup --watch"
}
}
```
### [tsup.config.ts](https://www.pkgpulse.com/blog/tsup-vs-tsdown-vs-unbuild-typescript-library-bundling-2026\#tsupconfigts)
```typescript
import { defineConfig } from "tsup"
export default defineConfig({
entry: ["src/index.ts"], // Entry point(s)
format: ["esm", "cjs"], // Output ESM + CommonJS
dts: true, // Generate .d.ts files
splitting: false, // Code splitting (for multiple entry points)
sourcemap: true, // Generate source maps
clean: true, // Clean dist/ before build
minify: false, // Don't minify libraries (let consumers decide)
external: ["react", "vue"], // Don't bundle peer dependencies
treeshake: true, // Remove unused code
target: "es2020", // Output target
outDir: "dist",
})
```
### [Multiple entry points](https://www.pkgpulse.com/blog/tsup-vs-tsdown-vs-unbuild-typescript-library-bundling-2026\#multiple-entry-points)
```typescript
import { defineConfig } from "tsup"
export default defineConfig({
// Multiple entry points (for sub-path exports):
entry: {
index: "src/index.ts",
server: "src/server.ts",
client: "src/client.ts",
},
format: ["esm", "cjs"],
dts: true,
splitting: true, // Share code between entry points
})
// Generates:
// dist/index.mjs + dist/index.js + dist/index.d.ts
// dist/server.mjs + dist/server.js + dist/server.d.ts
// dist/client.mjs + dist/client.js + dist/client.d.ts
```
### [package.json for dual ESM/CJS](https://www.pkgpulse.com/blog/tsup-vs-tsdown-vs-unbuild-typescript-library-bundling-2026\#packagejson-for-dual-esmcjs)
```json
{
"name": "my-library",
"version": "1.0.0",
"main": "./dist/index.js", // CJS entry (legacy)
"module": "./dist/index.mjs", // ESM entry (bundlers)
"types": "./dist/index.d.ts", // TypeScript types
"exports": {
".": {
"import": {
"types": "./dist/index.d.mts",
"default": "./dist/index.mjs"
},
"require": {
"types": "./dist/index.d.ts",
"default": "./dist/index.js"
}
},
"./server": {
"import": "./dist/server.mjs",
"require": "./dist/server.js"
}
},
"files": ["dist"],
"scripts": {
"build": "tsup",
"prepublishOnly": "npm run build"
},
"devDependencies": {
"tsup": "^8.0.0",
"typescript": "^5.0.0"
}
}
```
### [Watch mode for development](https://www.pkgpulse.com/blog/tsup-vs-tsdown-vs-unbuild-typescript-library-bundling-2026\#watch-mode-for-development)
```bash
# Rebuild on file changes:
tsup --watch
# Or in parallel with your dev server:
# package.json:
{
"scripts": {
"dev": "concurrently \"tsup --watch\" \"node dist/index.js\""
}
}
```
* * *
## [tsdown](https://www.pkgpulse.com/blog/tsup-vs-tsdown-vs-unbuild-typescript-library-bundling-2026\#tsdown)
[tsdown](https://tsdown.egoist.dev/) — the Rolldown-based tsup successor:
### [Why tsdown is faster](https://www.pkgpulse.com/blog/tsup-vs-tsdown-vs-unbuild-typescript-library-bundling-2026\#why-tsdown-is-faster)
```
tsup uses: esbuild (Go) → fast, but JS orchestration overhead
tsdown uses: Rolldown (Rust) → faster bundler + faster orchestration
Build time comparison (real-world library with 50 files):
tsup: ~2.5s
tsdown: ~0.6s
(varies by project size and machine)
```
### [Setup (nearly identical to tsup)](https://www.pkgpulse.com/blog/tsup-vs-tsdown-vs-unbuild-typescript-library-bundling-2026\#setup-nearly-identical-to-tsup)
```typescript
// tsdown.config.ts
import { defineConfig } from "tsdown"
export default defineConfig({
entry: ["src/index.ts"],
format: ["esm", "cjs"],
dts: true,
clean: true,
sourcemap: true,
external: ["react"],
})
```
```bash
# Commands are the same as tsup:
npx tsdown # Build
npx tsdown --watch # Watch mode
```
### [tsup → tsdown migration](https://www.pkgpulse.com/blog/tsup-vs-tsdown-vs-unbuild-typescript-library-bundling-2026\#tsup--tsdown-migration)
```bash
# Install:
npm uninstall tsup
npm install -D tsdown
# Rename config file:
mv tsup.config.ts tsdown.config.ts
# Update import:
# - import { defineConfig } from "tsup"
# + import { defineConfig } from "tsdown"
# Update package.json scripts:
# - "build": "tsup"
# + "build": "tsdown"
```
* * *
## [unbuild](https://www.pkgpulse.com/blog/tsup-vs-tsdown-vs-unbuild-typescript-library-bundling-2026\#unbuild)
[unbuild](https://github.com/unjs/unbuild) — UnJS library bundler:
### [Setup](https://www.pkgpulse.com/blog/tsup-vs-tsdown-vs-unbuild-typescript-library-bundling-2026\#setup-1)
```typescript
// build.config.ts
import { defineBuildConfig } from "unbuild"
export default defineBuildConfig({
entries: ["src/index"],
rollup: {
emitCJS: true, // Also emit CommonJS
},
declaration: true, // Generate .d.ts
clean: true,
})
```
```bash
# Build:
npx unbuild
# Stub mode (development):
npx unbuild --stub
```
### [Stub mode (unique to unbuild)](https://www.pkgpulse.com/blog/tsup-vs-tsdown-vs-unbuild-typescript-library-bundling-2026\#stub-mode-unique-to-unbuild)
```typescript
// "Stub mode" — generates proxy files that require/import the source directly
// No build step needed during development!
// dist/index.mjs (stub):
// export * from "../src/index.ts"
// dist/index.js (stub):
// module.exports = require("../src/index.ts") // via jiti
// Benefits:
// - No watch mode needed — file changes are picked up immediately
// - Faster feedback loop when developing a library locally
// - Link the package to a consumer with npm link — changes are live
// Production build:
// npx unbuild ← produces real bundles (no stub)
```
### [Multiple presets](https://www.pkgpulse.com/blog/tsup-vs-tsdown-vs-unbuild-typescript-library-bundling-2026\#multiple-presets)
```typescript
import { defineBuildConfig } from "unbuild"
export default defineBuildConfig([\
// Main package:\
{\
entries: ["src/index"],\
declaration: true,\
rollup: { emitCJS: true },\
},\
// CLI entry (no types needed):\
{\
entries: [{ input: "src/cli", name: "cli" }],\
declaration: false,\
rollup: {\
emitCJS: false,\
inlineDependencies: true, // Bundle everything into the CLI binary\
},\
},\
])
```
### [Used by the UnJS ecosystem](https://www.pkgpulse.com/blog/tsup-vs-tsdown-vs-unbuild-typescript-library-bundling-2026\#used-by-the-unjs-ecosystem)
```
unbuild is used by:
- nuxt → @nuxt/... packages
- nitro → the Nuxt server engine
- h3 → the HTTP framework
- ofetch → the fetch wrapper
- Most @unjs/* packages
If you're contributing to or building in this ecosystem, unbuild
is the natural choice.
```
* * *
## [Feature Comparison](https://www.pkgpulse.com/blog/tsup-vs-tsdown-vs-unbuild-typescript-library-bundling-2026\#feature-comparison)
| Feature | tsup | tsdown | unbuild |
| --- | --- | --- | --- |
| Build engine | esbuild (Go) | Rolldown (Rust) | Rollup (JS) |
| Build speed | Fast | ⚡ Fastest | Moderate |
| ESM + CJS | ✅ | ✅ | ✅ |
| .d.ts generation | ✅ | ✅ | ✅ |
| Stub mode (no build) | ❌ | ❌ | ✅ |
| Code splitting | ✅ | ✅ | ✅ |
| treeshake | ✅ | ✅ | ✅ |
| Plugin ecosystem | esbuild plugins | Rolldown plugins | Rollup plugins |
| TypeScript config | tsup.config.ts | tsdown.config.ts | build.config.ts |
| Community size | ⭐ Large | Growing fast | Medium |
| Weekly downloads | ~6M | ~500K | ~3M |
* * *
## [When to Use Each](https://www.pkgpulse.com/blog/tsup-vs-tsdown-vs-unbuild-typescript-library-bundling-2026\#when-to-use-each)
**Choose tsup if:**
- The safe, battle-tested choice — most tutorials and examples use it
- Large community, most Stack Overflow answers, most plugins
- Works for 95% of library use cases out of the box
**Choose tsdown if:**
- Build speed is a priority (large libraries, frequent CI builds)
- You're migrating from tsup — API is nearly identical
- On the cutting edge of tooling in 2026
**Choose unbuild if:**
- Working in the Nuxt, Nitro, or UnJS ecosystem
- Want stub mode for instant development without watch rebuilds
- Need Rollup-specific plugins not available in esbuild/Rolldown
**Also consider:**
- **Vite Library Mode** — for libraries that need Vite plugins (CSS modules, etc.)
- **pkgroll** — minimal bundler for packages with simple needs
- **microbundle** — smaller alternative, but less actively maintained in 2026
* * *
## [Methodology](https://www.pkgpulse.com/blog/tsup-vs-tsdown-vs-unbuild-typescript-library-bundling-2026\#methodology)
Download data from npm registry (weekly average, February 2026). Feature comparison based on tsup v8.x, tsdown v0.x, and unbuild v2.x.
_[Compare build tooling and bundler packages on PkgPulse →](https://www.pkgpulse.com/)_
## Comments
### The 2026 JavaScript Stack Cheatsheet
One PDF: the best package for every category (ORMs, bundlers, auth, testing, state management). Used by 500+ devs. Free, updated monthly.
Get the Free Cheatsheet

View File

@ -1,705 +0,0 @@
[Skip to main content](https://www.pkgpulse.com/blog/floating-ui-vs-tippyjs-vs-radix-tooltip-popover-2026#main-content)
# [Floating UI vs Tippy.js vs Radix Tooltip: Popover Positioning 2026](https://www.pkgpulse.com/blog/floating-ui-vs-tippyjs-vs-radix-tooltip-popover-2026\#floating-ui-vs-tippyjs-vs-radix-tooltip-popover-positioning-2026)
## [TL;DR](https://www.pkgpulse.com/blog/floating-ui-vs-tippyjs-vs-radix-tooltip-popover-2026\#tldr)
Building tooltips, popovers, dropdowns, and floating menus correctly is deceptively hard — viewport overflow, collision detection, scroll containers, and keyboard accessibility are all gotchas that custom solutions routinely miss. **Floating UI** (successor to Popper.js from the same authors) is the low-level positioning engine — pure geometry and collision detection, totally unstyled, works with any framework, and is what Radix, Mantine, and many others use internally. **Tippy.js** is the batteries-included tooltip library built on Popper.js — styled out of the box, declarative API, animates, works in vanilla JS and React — but it's showing its age in 2026 with no App Router support and weaker accessibility guarantees. **Radix UI's Tooltip and Popover** are headless, fully accessible (WAI-ARIA compliant), React-only components built on Floating UI internally — the correct choice for React/Next.js component libraries where accessibility is non-negotiable. For low-level control over positioning in any framework: Floating UI. For quick tooltips with minimal config: Tippy.js. For production React UIs that must be accessible: Radix Tooltip/Popover.
## [Key Takeaways](https://www.pkgpulse.com/blog/floating-ui-vs-tippyjs-vs-radix-tooltip-popover-2026\#key-takeaways)
- **Floating UI is framework-agnostic** — core is vanilla JS, `@floating-ui/react` adds React hooks
- **Floating UI handles all edge cases** — viewport overflow, flip, shift, arrow, virtual elements
- **Tippy.js is easiest to get started**`<Tippy content="Tooltip">` wraps any element
- **Radix Tooltip is fully WAI-ARIA compliant** — focus management, screen readers, keyboard nav
- **Tippy.js is built on Popper.js** — Floating UI's predecessor, still maintained but less active
- **Radix Popover manages open state** — controlled and uncontrolled modes, portal rendering
- **Floating UI powers Radix internally** — Radix uses `@floating-ui/react-dom` under the hood
* * *
## [Use Case Map](https://www.pkgpulse.com/blog/floating-ui-vs-tippyjs-vs-radix-tooltip-popover-2026\#use-case-map)
```
Simple tooltip on hover → Tippy.js or Radix Tooltip
Tooltip with custom render → Floating UI or Radix Tooltip
Accessible popover with content → Radix Popover
Dropdown menu with keyboard nav → Radix DropdownMenu
Custom positioning engine → Floating UI (raw)
Framework-agnostic tooltip → Tippy.js or Floating UI
Select/Combobox overlay → Floating UI or Radix Select
Context menu (right-click) → Radix ContextMenu
```
* * *
## [Floating UI: The Positioning Engine](https://www.pkgpulse.com/blog/floating-ui-vs-tippyjs-vs-radix-tooltip-popover-2026\#floating-ui-the-positioning-engine)
Floating UI provides the geometry and collision detection algorithms — you wire up the DOM refs and React state yourself.
### [Installation](https://www.pkgpulse.com/blog/floating-ui-vs-tippyjs-vs-radix-tooltip-popover-2026\#installation)
```bash
npm install @floating-ui/react
# For vanilla JS (no React):
npm install @floating-ui/dom
```
### [Basic Tooltip](https://www.pkgpulse.com/blog/floating-ui-vs-tippyjs-vs-radix-tooltip-popover-2026\#basic-tooltip)
```tsx
import {
useFloating,
autoUpdate,
offset,
flip,
shift,
useHover,
useFocus,
useDismiss,
useRole,
useInteractions,
FloatingPortal,
} from "@floating-ui/react";
import { useState } from "react";
interface TooltipProps {
content: string;
children: React.ReactElement;
}
export function Tooltip({ content, children }: TooltipProps) {
const [isOpen, setIsOpen] = useState(false);
const { refs, floatingStyles, context } = useFloating({
open: isOpen,
onOpenChange: setIsOpen,
placement: "top",
// Keep in sync with scroll and resize
whileElementsMounted: autoUpdate,
middleware: [\
offset(8), // Distance from reference\
flip(), // Flip to bottom if no space above\
shift({ padding: 8 }), // Shift horizontally to stay in viewport\
],
});
// Interaction hooks — compose behaviors
const hover = useHover(context, { move: false });
const focus = useFocus(context);
const dismiss = useDismiss(context);
const role = useRole(context, { role: "tooltip" });
const { getReferenceProps, getFloatingProps } = useInteractions([\
hover,\
focus,\
dismiss,\
role,\
]);
return (
<>
{/* Attach to trigger element */}
{React.cloneElement(children, {
ref: refs.setReference,
...getReferenceProps(),
})}
{/* Tooltip — rendered in portal to escape stacking contexts */}
<FloatingPortal>
{isOpen && (
<div
ref={refs.setFloating}
style={{
...floatingStyles,
background: "#1a1a1a",
color: "#fff",
padding: "4px 8px",
borderRadius: 4,
fontSize: 12,
zIndex: 9999,
}}
{...getFloatingProps()}
>
{content}
</div>
)}
</FloatingPortal>
</>
);
}
// Usage
<Tooltip content="Copy to clipboard">
<button>Copy</button>
</Tooltip>
```
### [Arrow Placement](https://www.pkgpulse.com/blog/floating-ui-vs-tippyjs-vs-radix-tooltip-popover-2026\#arrow-placement)
```tsx
import {
useFloating,
arrow,
offset,
flip,
FloatingArrow,
} from "@floating-ui/react";
import { useRef } from "react";
export function TooltipWithArrow({ content, children }: TooltipProps) {
const arrowRef = useRef<SVGSVGElement>(null);
const { refs, floatingStyles, context, middlewareData, placement } = useFloating({
middleware: [\
offset(10),\
flip(),\
arrow({ element: arrowRef }),\
],
});
return (
<>
<div ref={refs.setReference}>{children}</div>
<div ref={refs.setFloating} style={floatingStyles}>
{content}
{/* FloatingArrow renders an SVG arrow positioned correctly */}
<FloatingArrow
ref={arrowRef}
context={context}
fill="#1a1a1a"
height={8}
width={14}
/>
</div>
</>
);
}
```
### [Popover (Click-to-Open)](https://www.pkgpulse.com/blog/floating-ui-vs-tippyjs-vs-radix-tooltip-popover-2026\#popover-click-to-open)
```tsx
import {
useFloating,
autoUpdate,
offset,
flip,
shift,
useClick,
useDismiss,
useRole,
useInteractions,
FloatingPortal,
FloatingFocusManager,
} from "@floating-ui/react";
import { useState } from "react";
export function Popover({ trigger, content }: { trigger: React.ReactNode; content: React.ReactNode }) {
const [isOpen, setIsOpen] = useState(false);
const { refs, floatingStyles, context } = useFloating({
open: isOpen,
onOpenChange: setIsOpen,
placement: "bottom-start",
whileElementsMounted: autoUpdate,
middleware: [offset(4), flip(), shift({ padding: 8 })],
});
const click = useClick(context);
const dismiss = useDismiss(context);
const role = useRole(context, { role: "dialog" });
const { getReferenceProps, getFloatingProps } = useInteractions([click, dismiss, role]);
return (
<>
<div ref={refs.setReference} {...getReferenceProps()}>
{trigger}
</div>
<FloatingPortal>
{isOpen && (
// FloatingFocusManager traps focus inside the popover
<FloatingFocusManager context={context} modal={false}>
<div
ref={refs.setFloating}
style={{
...floatingStyles,
background: "#fff",
border: "1px solid #e2e8f0",
borderRadius: 8,
boxShadow: "0 4px 20px rgba(0,0,0,0.15)",
padding: 16,
zIndex: 9999,
minWidth: 200,
}}
{...getFloatingProps()}
>
{content}
</div>
</FloatingFocusManager>
)}
</FloatingPortal>
</>
);
}
```
### [Virtual Element (Context Menu)](https://www.pkgpulse.com/blog/floating-ui-vs-tippyjs-vs-radix-tooltip-popover-2026\#virtual-element-context-menu)
```tsx
import { useFloating, offset, flip, shift, useClientPoint, useInteractions } from "@floating-ui/react";
import { useState } from "react";
export function ContextMenu({ items }: { items: string[] }) {
const [isOpen, setIsOpen] = useState(false);
const { refs, floatingStyles, context } = useFloating({
open: isOpen,
onOpenChange: setIsOpen,
placement: "bottom-start",
middleware: [offset({ mainAxis: 5, alignmentAxis: 4 }), flip(), shift()],
});
// Follow the mouse cursor
const clientPoint = useClientPoint(context);
const { getReferenceProps, getFloatingProps } = useInteractions([clientPoint]);
return (
<div
ref={refs.setReference}
onContextMenu={(e) => {
e.preventDefault();
setIsOpen(true);
}}
style={{ minHeight: 200, border: "1px dashed #ccc", padding: 16 }}
{...getReferenceProps()}
>
Right-click anywhere here
{isOpen && (
<div
ref={refs.setFloating}
style={{
...floatingStyles,
background: "#fff",
border: "1px solid #e2e8f0",
borderRadius: 6,
boxShadow: "0 2px 10px rgba(0,0,0,0.12)",
zIndex: 9999,
}}
{...getFloatingProps()}
>
{items.map((item) => (
<button
key={item}
style={{ display: "block", width: "100%", padding: "8px 16px", textAlign: "left" }}
onClick={() => setIsOpen(false)}
>
{item}
</button>
))}
</div>
)}
</div>
);
}
```
* * *
## [Tippy.js: Batteries-Included Tooltips](https://www.pkgpulse.com/blog/floating-ui-vs-tippyjs-vs-radix-tooltip-popover-2026\#tippyjs-batteries-included-tooltips)
Tippy.js provides a complete tooltip and popover solution with themes, animations, and a declarative API — minimal configuration required.
### [Installation](https://www.pkgpulse.com/blog/floating-ui-vs-tippyjs-vs-radix-tooltip-popover-2026\#installation-1)
```bash
npm install tippy.js @tippyjs/react
```
### [Basic Usage](https://www.pkgpulse.com/blog/floating-ui-vs-tippyjs-vs-radix-tooltip-popover-2026\#basic-usage)
```tsx
import Tippy from "@tippyjs/react";
import "tippy.js/dist/tippy.css"; // Default theme
export function CopyButton() {
return (
<Tippy content="Copy to clipboard">
<button onClick={() => navigator.clipboard.writeText("text")}>
Copy
</button>
</Tippy>
);
}
```
### [Placement and Options](https://www.pkgpulse.com/blog/floating-ui-vs-tippyjs-vs-radix-tooltip-popover-2026\#placement-and-options)
```tsx
import Tippy from "@tippyjs/react";
export function FeatureTooltips() {
return (
<div>
<Tippy content="Shows above" placement="top">
<button>Top</button>
</Tippy>
<Tippy content="Shows on the right" placement="right">
<button>Right</button>
</Tippy>
{/* Delay: 300ms show, 100ms hide */}
<Tippy content="Delayed tooltip" delay={[300, 100]}>
<button>Delayed</button>
</Tippy>
{/* Click to toggle instead of hover */}
<Tippy content="Click me" trigger="click" interactive>
<button>Click</button>
</Tippy>
{/* Interactive (won't close when hovering tooltip) */}
<Tippy
content={
<div>
<strong>Rich content</strong>
<p>With multiple elements</p>
<a href="/docs">Read more</a>
</div>
}
interactive
interactiveBorder={20}
placement="bottom"
>
<button>Hover for rich tooltip</button>
</Tippy>
{/* Disabled */}
<Tippy content="Tooltip" disabled={false}>
<span>
<button disabled>Disabled Button</button>
</span>
</Tippy>
</div>
);
}
```
### [Animations and Themes](https://www.pkgpulse.com/blog/floating-ui-vs-tippyjs-vs-radix-tooltip-popover-2026\#animations-and-themes)
```tsx
import Tippy from "@tippyjs/react";
import "tippy.js/dist/tippy.css";
import "tippy.js/animations/scale.css";
import "tippy.js/themes/light.css";
import "tippy.js/themes/material.css";
export function ThemedTooltips() {
return (
<>
{/* Built-in light theme */}
<Tippy content="Light theme" theme="light">
<button>Light</button>
</Tippy>
{/* Scale animation */}
<Tippy content="Animated" animation="scale">
<button>Scale</button>
</Tippy>
{/* Custom theme via CSS */}
<Tippy content="Custom theme" className="custom-tippy">
<button>Custom</button>
</Tippy>
</>
);
}
```
### [Controlled Tippy](https://www.pkgpulse.com/blog/floating-ui-vs-tippyjs-vs-radix-tooltip-popover-2026\#controlled-tippy)
```tsx
import Tippy from "@tippyjs/react";
import { useState } from "react";
export function ControlledTooltip() {
const [visible, setVisible] = useState(false);
return (
<Tippy
content="This is controlled"
visible={visible}
onClickOutside={() => setVisible(false)}
interactive
>
<button onClick={() => setVisible((v) => !v)}>
{visible ? "Hide" : "Show"} Tooltip
</button>
</Tippy>
);
}
```
* * *
## [Radix UI Tooltip and Popover: Accessible Components](https://www.pkgpulse.com/blog/floating-ui-vs-tippyjs-vs-radix-tooltip-popover-2026\#radix-ui-tooltip-and-popover-accessible-components)
Radix provides fully accessible, headless components with correct ARIA roles, focus management, and keyboard navigation — you bring your own styles.
### [Installation](https://www.pkgpulse.com/blog/floating-ui-vs-tippyjs-vs-radix-tooltip-popover-2026\#installation-2)
```bash
npm install @radix-ui/react-tooltip @radix-ui/react-popover
```
### [Tooltip](https://www.pkgpulse.com/blog/floating-ui-vs-tippyjs-vs-radix-tooltip-popover-2026\#tooltip)
```tsx
import * as Tooltip from "@radix-ui/react-tooltip";
// Provider wraps your app — controls delay behavior globally
export function App() {
return (
<Tooltip.Provider delayDuration={300} skipDelayDuration={500}>
<YourApp />
</Tooltip.Provider>
);
}
// Individual tooltip
export function DeleteButton() {
return (
<Tooltip.Root>
<Tooltip.Trigger asChild>
<button className="icon-button" aria-label="Delete item">
🗑️
</button>
</Tooltip.Trigger>
<Tooltip.Portal>
<Tooltip.Content
className="tooltip-content"
sideOffset={4}
side="top"
align="center"
>
Delete item
<Tooltip.Arrow className="tooltip-arrow" />
</Tooltip.Content>
</Tooltip.Portal>
</Tooltip.Root>
);
}
// CSS
/*
.tooltip-content {
background: #1a1a1a;
color: white;
border-radius: 4px;
padding: 4px 10px;
font-size: 13px;
box-shadow: 0 2px 8px rgba(0,0,0,0.2);
animation-duration: 150ms;
animation-timing-function: cubic-bezier(0.16, 1, 0.3, 1);
will-change: transform, opacity;
}
.tooltip-content[data-state='delayed-open'][data-side='top'] {
animation-name: slideDownAndFade;
}
@keyframes slideDownAndFade {
from { opacity: 0; transform: translateY(2px); }
to { opacity: 1; transform: translateY(0); }
}
.tooltip-arrow {
fill: #1a1a1a;
}
*/
```
### [Popover](https://www.pkgpulse.com/blog/floating-ui-vs-tippyjs-vs-radix-tooltip-popover-2026\#popover)
```tsx
import * as Popover from "@radix-ui/react-popover";
export function FilterPopover() {
return (
<Popover.Root>
<Popover.Trigger asChild>
<button className="filter-button">Filters ⚙️</button>
</Popover.Trigger>
<Popover.Portal>
<Popover.Content
className="popover-content"
sideOffset={4}
align="start"
// Prevent closing when focus moves inside popover
onOpenAutoFocus={(e) => e.preventDefault()}
>
<div className="filter-form">
<h3>Filter Options</h3>
<label>
Status
<select>
<option>All</option>
<option>Active</option>
<option>Inactive</option>
</select>
</label>
<label>
Date Range
<input type="date" />
</label>
<div className="filter-actions">
<button>Reset</button>
<Popover.Close asChild>
<button>Apply</button>
</Popover.Close>
</div>
</div>
<Popover.Arrow className="popover-arrow" />
<Popover.Close className="popover-close" aria-label="Close">
</Popover.Close>
</Popover.Content>
</Popover.Portal>
</Popover.Root>
);
}
```
### [Tooltip with Tailwind (shadcn/ui Pattern)](https://www.pkgpulse.com/blog/floating-ui-vs-tippyjs-vs-radix-tooltip-popover-2026\#tooltip-with-tailwind-shadcnui-pattern)
```tsx
// components/ui/tooltip.tsx — shadcn/ui Tooltip component
import * as TooltipPrimitive from "@radix-ui/react-tooltip";
import { cn } from "@/lib/utils";
const TooltipProvider = TooltipPrimitive.Provider;
const TooltipRoot = TooltipPrimitive.Root;
const TooltipTrigger = TooltipPrimitive.Trigger;
const TooltipContent = React.forwardRef<
React.ElementRef<typeof TooltipPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Content>
>(({ className, sideOffset = 4, ...props }, ref) => (
<TooltipPrimitive.Portal>
<TooltipPrimitive.Content
ref={ref}
sideOffset={sideOffset}
className={cn(
"z-50 overflow-hidden rounded-md bg-primary px-3 py-1.5 text-xs text-primary-foreground animate-in fade-in-0 zoom-in-95",
"data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95",
"data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2",
"data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
className
)}
{...props}
/>
</TooltipPrimitive.Portal>
));
TooltipContent.displayName = TooltipPrimitive.Content.displayName;
// Export the Tooltip component
export function Tooltip({
children,
content,
...props
}: {
children: React.ReactNode;
content: React.ReactNode;
} & React.ComponentPropsWithoutRef<typeof TooltipRoot>) {
return (
<TooltipProvider>
<TooltipRoot {...props}>
<TooltipTrigger asChild>{children}</TooltipTrigger>
<TooltipContent>{content}</TooltipContent>
</TooltipRoot>
</TooltipProvider>
);
}
// Usage with Tailwind
<Tooltip content="Settings">
<button>⚙️</button>
</Tooltip>
```
* * *
## [Feature Comparison](https://www.pkgpulse.com/blog/floating-ui-vs-tippyjs-vs-radix-tooltip-popover-2026\#feature-comparison)
| Feature | Floating UI | Tippy.js | Radix Tooltip/Popover |
| --- | --- | --- | --- |
| **Framework** | Any (React, Vue, Svelte) | Any + React | React only |
| **Styling** | Unstyled (bring your own) | Styled (override available) | Unstyled (bring your own) |
| **Accessibility** | Manual (you implement) | Basic | ✅ WAI-ARIA compliant |
| **Focus trap** | `FloatingFocusManager` | No | ✅ Built-in |
| **Keyboard nav** | Via hooks | Basic | ✅ Built-in |
| **Collision detection** | ✅ Advanced | ✅ Via Popper.js | ✅ Via Floating UI |
| **Arrow positioning** | ✅ `FloatingArrow` | ✅ Built-in | ✅ `Tooltip.Arrow` |
| **Animations** | CSS (you define) | ✅ Built-in themes | CSS data-state |
| **Portal** | ✅ `FloatingPortal` | ✅ Auto | ✅ `Portal` |
| **Virtual elements** | ✅ | Limited | No |
| **Bundle size** | ~10kB | ~15kB | ~8kB per primitive |
| **npm weekly** | 12M | 3M | 8M (tooltip) |
| **GitHub stars** | 29k | 11k | 22k (radix-ui/primitives) |
| **TypeScript** | ✅ Full | ✅ | ✅ Full |
* * *
## [When to Use Each](https://www.pkgpulse.com/blog/floating-ui-vs-tippyjs-vs-radix-tooltip-popover-2026\#when-to-use-each)
**Choose Floating UI if:**
- Building a component library from scratch (unstyled primitives)
- Need maximum control over positioning behavior and styling
- Framework-agnostic — Vue, Svelte, vanilla JS, or React
- Virtual element positioning (context menus, cursors)
- Complex middleware requirements (custom offset logic)
- Want to understand exactly what's happening — no magic
**Choose Tippy.js if:**
- Quick tooltip needed with minimal setup
- Vanilla JS project or legacy codebase
- Want built-in themes and animations without CSS work
- Simple hover tooltips where accessibility is secondary
- Prototyping or internal tools where ARIA isn't critical
**Choose Radix Tooltip/Popover if:**
- React/Next.js production application
- Accessibility is required — screen readers, keyboard navigation
- Using shadcn/ui (Radix is the foundation)
- Want compound component API with proper focus management
- Need `asChild` pattern to avoid extra DOM elements
- Building a design system where consumers control all styling
* * *
## [Methodology](https://www.pkgpulse.com/blog/floating-ui-vs-tippyjs-vs-radix-tooltip-popover-2026\#methodology)
Data sourced from Floating UI documentation (floating-ui.com/docs), Tippy.js documentation (atomiks.github.io/tippyjs), Radix UI documentation (radix-ui.com/docs), npm weekly download statistics as of February 2026, GitHub star counts as of February 2026, and community discussions from the React Discord, CSS-Tricks, and web accessibility forums.
* * *
_Related: [Radix UI vs Headless UI vs Ariakit](https://www.pkgpulse.com/blog/radix-ui-vs-headless-ui-vs-ariakit-accessible-react-components-2026) for broader headless component comparisons, or [shadcn/ui vs Mantine vs Chakra UI](https://www.pkgpulse.com/blog/shadcn-vs-mantine-vs-chakra-react-component-library-2026) for styled React component libraries._
## Comments
### The 2026 JavaScript Stack Cheatsheet
One PDF: the best package for every category (ORMs, bundlers, auth, testing, state management). Used by 500+ devs. Free, updated monthly.
Get the Free Cheatsheet

View File

@ -1,305 +0,0 @@
[Skip to main content](https://www.pkgpulse.com/blog/biome-vs-eslint-prettier-linting-2026#main-content)
## [TL;DR](https://www.pkgpulse.com/blog/biome-vs-eslint-prettier-linting-2026\#tldr)
**Biome is production-ready for most JavaScript and TypeScript projects, but ESLint + Prettier is still the right call if you need the full ESLint plugin ecosystem.** Biome's 25x speed advantage is real and meaningful in CI. The formatter is nearly identical to Prettier. The linter covers ~80% of common ESLint rules. What Biome can't replace yet: type-aware lint rules (requires TypeScript language service), framework-specific plugins (eslint-plugin-react-hooks, eslint-plugin-next), and any custom ESLint rules your team has written. Verdict: new projects → Biome. Existing projects with heavy plugin usage → evaluate the gap before switching.
## [Key Takeaways](https://www.pkgpulse.com/blog/biome-vs-eslint-prettier-linting-2026\#key-takeaways)
- **Speed**: Biome formats and lints in ~50ms; ESLint + Prettier takes ~2-3s for same project
- **Coverage**: ~250 lint rules (growing); ESLint has 1000+ with the plugin ecosystem
- **Prettier compatibility**: Biome's formatter matches Prettier output for ~96% of cases
- **Not yet in Biome**: type-aware rules, React Hooks rules, Next.js plugin, custom rule authoring (in roadmap)
- **Configuration**: one config file (`biome.json`) vs two separate configs
* * *
## [Speed: The Main Reason to Switch](https://www.pkgpulse.com/blog/biome-vs-eslint-prettier-linting-2026\#speed-the-main-reason-to-switch)
```bash
# Real benchmark — a Next.js project with ~150 TypeScript files:
# ESLint + Prettier (separate runs):
npx eslint . --ext .ts,.tsx → 3.2 seconds
npx prettier --check "**/*.{ts,tsx}" → 1.1 seconds
Total: ~4.3 seconds
# Biome (lint + format together):
npx biome check . → 0.18 seconds
# 24x faster combined
# CI impact (running on every PR):
# ESLint + Prettier: 4-5 seconds of your CI job
# Biome: ~0.2 seconds
# Pre-commit hooks (runs on staged files):
# ESLint + Prettier (lint-staged): ~1.5s per commit
# Biome: ~0.05s per commit
# Developer experience: the difference between "imperceptible" and "I notice this every time"
# Why Biome is faster:
# → Rust implementation (not Node.js)
# → Parallel processing of files
# → Single pass: lint + format in one traversal
# → No plugin loading overhead (rules are compiled in)
```
* * *
## [Setup: One Config vs Two Configs](https://www.pkgpulse.com/blog/biome-vs-eslint-prettier-linting-2026\#setup-one-config-vs-two-configs)
```json
// Biome — biome.json (one file for everything):
{
"$schema": "https://biomejs.dev/schemas/1.9.0/schema.json",
"organizeImports": {
"enabled": true
},
"linter": {
"enabled": true,
"rules": {
"recommended": true,
"correctness": {
"noUnusedVariables": "error"
},
"style": {
"noParameterAssign": "warn"
},
"nursery": {
"useSortedClasses": "warn"
}
}
},
"formatter": {
"enabled": true,
"indentStyle": "space",
"indentWidth": 2,
"lineWidth": 100
},
"javascript": {
"formatter": {
"quoteStyle": "double",
"trailingCommas": "all",
"semicolons": "always"
}
},
"files": {
"ignore": ["node_modules", "dist", ".next"]
}
}
// Compare to ESLint + Prettier:
// .eslintrc.json OR eslint.config.mjs (ESLint 9 flat config)
// .prettierrc
// .prettierignore
// .eslintignore
// package.json scripts to run both
// lint-staged config for pre-commit hooks
// Biome replaces all of that with one file.
```
* * *
## [Lint Rules Coverage](https://www.pkgpulse.com/blog/biome-vs-eslint-prettier-linting-2026\#lint-rules-coverage)
```
Biome rules (v1.9, 2026) — grouped by ESLint equivalent:
✅ Covered well:
→ no-unused-vars, no-undef → biome: correctness/noUnusedVariables
→ no-console → biome: suspicious/noConsole
→ eqeqeq → biome: suspicious/noDoubleEquals
→ no-var → biome: style/noVar
→ prefer-const → biome: style/useConst
→ no-empty → biome: correctness/noEmptyBlockStatements
→ no-duplicate-imports → biome: correctness/noDuplicateObjectKeys
→ arrow-body-style → biome: style/useArrowFunction
→ object-shorthand → biome: style/useShorthandAssign
→ 200+ more rules...
⚠️ Partially covered / different API:
→ import/order → biome: organizeImports (reorders, doesn't configure)
→ jsx-a11y/* → basic accessibility rules, not all of jsx-a11y
❌ Not yet in Biome:
→ Type-aware rules (requires TypeScript type checker)
@typescript-eslint/no-floating-promises
@typescript-eslint/no-misused-promises
@typescript-eslint/consistent-return (typed)
→ eslint-plugin-react-hooks (useEffect deps, hooks rules)
→ eslint-plugin-next (app router patterns, image optimization rules)
→ eslint-plugin-import/no-cycle (circular dependency detection)
→ Custom rules your team wrote
The gap is real but smaller than it was.
Most "critical" lint rules are covered.
The missing ones are important for React/Next.js specifically.
```
* * *
## [Prettier Compatibility: How Close Is It?](https://www.pkgpulse.com/blog/biome-vs-eslint-prettier-linting-2026\#prettier-compatibility-how-close-is-it)
```javascript
// Biome's formatter is designed to match Prettier's output
// Real-world compatibility: ~96% identical for JS/TS
// Cases where they differ (edge cases):
// 1. Long template literals
const query = `SELECT * FROM users WHERE id = ${userId} AND status = 'active' AND created_at > '2024-01-01'`;
// Prettier: keeps on one line if fits, wraps differently
// Biome: similar but not identical on complex expressions
// 2. Decorators (TypeScript)
// Some class decorator formatting differs slightly
// 3. Complex JSX expressions
// Multi-line JSX attributes format slightly differently in edge cases
// For the vast majority of code: identical output.
// The 4% difference is in edge cases you'll rarely hit.
// Migration from Prettier:
# Run Biome formatter on your entire codebase once:
npx biome format --write .
# Check what changed (should be minimal):
git diff
# If there are many meaningful diffs, the code was inconsistently formatted.
# Biome will now be the source of truth.
# Teams commonly report:
# → 0-20 files changed on a typical codebase
# → Changes are whitespace/trailing comma in edge cases
# → No semantic code changes
```
* * *
## [Integration with Editors and CI](https://www.pkgpulse.com/blog/biome-vs-eslint-prettier-linting-2026\#integration-with-editors-and-ci)
```bash
# VS Code — install the Biome extension:
# Extensions: "Biome" (biomejs.biome)
# settings.json:
{
"editor.defaultFormatter": "biomejs.biome",
"editor.formatOnSave": true,
"[javascript]": { "editor.defaultFormatter": "biomejs.biome" },
"[typescript]": { "editor.defaultFormatter": "biomejs.biome" },
"[typescriptreact]": { "editor.defaultFormatter": "biomejs.biome" }
}
# The extension works well — fast, accurate
# Pre-commit hooks (replace lint-staged + eslint/prettier):
# package.json:
{
"scripts": {
"prepare": "simple-git-hooks"
},
"simple-git-hooks": {
"pre-commit": "npx lint-staged"
},
"lint-staged": {
"*.{js,ts,jsx,tsx,json,css}": "biome check --apply --no-errors-on-unmatched"
}
}
# CI (GitHub Actions):
- name: Lint and Format Check
run: npx biome ci .
# biome ci = check without --write; exits non-zero if issues found
# Replaces separate eslint and prettier --check steps
# The biome ci command is designed exactly for this use case.
```
* * *
## [Migration from ESLint + Prettier](https://www.pkgpulse.com/blog/biome-vs-eslint-prettier-linting-2026\#migration-from-eslint--prettier)
```bash
# Step 1: Initialize Biome
npm install --save-dev --save-exact @biomejs/biome
# Step 2: Generate config from existing ESLint config
npx biome migrate eslint --include-inspired
# --include-inspired: adds Biome rules "inspired by" your ESLint rules
# Step 3: Format with Biome once (commit this separately for clean history)
npx biome format --write .
git add -A && git commit -m "chore: migrate formatter to Biome"
# Step 4: Fix linting issues
npx biome check --apply .
# Some will be auto-fixed. Others need manual attention.
# Step 5: Find the ESLint rules you'll miss
# Go through your .eslintrc and categorize:
# → Rule covered by Biome? → Remove from ESLint
# → Rule is react-hooks or type-aware? → Keep ESLint for JUST those rules
# Step 6: The hybrid approach (if you need react-hooks rules):
# Keep ESLint for only what Biome doesn't cover:
# .eslintrc:
{
"plugins": ["react-hooks"],
"rules": {
"react-hooks/rules-of-hooks": "error",
"react-hooks/exhaustive-deps": "warn"
// Nothing else — Biome handles the rest
}
}
# This is the recommended migration path for React projects.
# Use Biome for formatting + most linting.
# Keep a minimal ESLint config for react-hooks only.
```
* * *
## [Verdict: Should You Switch?](https://www.pkgpulse.com/blog/biome-vs-eslint-prettier-linting-2026\#verdict-should-you-switch)
```
New greenfield project (2026):
→ Yes — use Biome from day one
→ Add minimal ESLint config for react-hooks if it's a React project
→ The speed win in CI is real; the single config is a DX improvement
→ The rule gap doesn't matter if you're starting fresh
Existing project (small, no complex ESLint plugins):
→ Yes — migrate. 2-4 hour job. Net positive.
→ Use the migrate command; review diffs; ship it
Existing project (React/Next.js, heavy plugin usage):
→ Hybrid approach — Biome for format + most lint, ESLint for react-hooks + next
→ Not "switch" but "add Biome alongside a reduced ESLint config"
→ You still get the speed benefit for most of the work
Existing project (custom ESLint rules, type-aware rules critical):
→ Not yet — monitor Biome's type-aware rule roadmap
→ Expected in late 2026 based on their public roadmap
→ Reevaluate in 6 months
The trajectory is clear: Biome is getting better fast.
The rule gap that seemed large in 2024 is substantially smaller in 2026.
Type-aware rules are the final frontier.
```
* * *
_Compare Biome, ESLint, and Prettier download trends at [PkgPulse](https://www.pkgpulse.com/)._
See the live comparison
[View biome vs. eslint on PkgPulse →](https://www.pkgpulse.com/compare/biome-vs-eslint)
## Comments
### The 2026 JavaScript Stack Cheatsheet
One PDF: the best package for every category (ORMs, bundlers, auth, testing, state management). Used by 500+ devs. Free, updated monthly.
Get the Free Cheatsheet

View File

@ -1,256 +0,0 @@
[Skip to main content](https://www.pkgpulse.com/blog/pnpm-vs-bun-vs-npm-package-manager-performance-2026#main-content)
## [TL;DR](https://www.pkgpulse.com/blog/pnpm-vs-bun-vs-npm-package-manager-performance-2026\#tldr)
**pnpm is the 2026 default for serious JavaScript projects — content-addressable store, strict dependency isolation, and the best monorepo support.** Bun is 5-10x faster than pnpm on installs but still has edge cases with niche packages. npm is the default that works everywhere but is the slowest. For new projects: pnpm (or Bun if you're already in the Bun ecosystem). For CI speed: Bun's install is often faster than even pnpm's cached install.
## [Key Takeaways](https://www.pkgpulse.com/blog/pnpm-vs-bun-vs-npm-package-manager-performance-2026\#key-takeaways)
- **Bun install**: 5-10x faster than pnpm, 15-25x faster than npm (measured on real projects)
- **pnpm**: Strictest isolation (prevents phantom dependencies), best workspace support, most compatible
- **npm**: Default, slowest, but universally compatible, `node_modules` phantom deps allowed
- **Disk usage**: pnpm uses ~50% less disk space vs npm (content-addressable store deduplication)
- **Monorepos**: pnpm workspaces > Bun workspaces > npm workspaces (feature parity gap)
- **2026 recommendation**: pnpm for serious projects; Bun install if on Bun runtime already
* * *
## [Downloads / Usage](https://www.pkgpulse.com/blog/pnpm-vs-bun-vs-npm-package-manager-performance-2026\#downloads--usage)
| Package Manager | Weekly Downloads | Trend |
| --- | --- | --- |
| `npm` | Default (Node.js) | → Stable |
| `pnpm` | ~7M downloads/week | ↑ Growing |
| `bun` | ~1.5M downloads/week | ↑ Fast growing |
* * *
## [Install Speed Benchmarks](https://www.pkgpulse.com/blog/pnpm-vs-bun-vs-npm-package-manager-performance-2026\#install-speed-benchmarks)
```
Benchmark: Next.js 15 project (1,847 packages)
Environment: M3 MacBook Pro, SSD, cold/warm cache
COLD INSTALL (no cache, no lockfile):
npm: 82s
pnpm: 31s (2.6x faster than npm)
Bun: 8s (10x faster than npm)
CACHED INSTALL (lockfile present, store exists):
npm: 45s (reads node_modules hash)
pnpm: 4s (hardlinks from content store)
Bun: 0.8s (binary cache, near-instant)
CI INSTALL (lockfile present, fresh machine):
npm: 62s
pnpm: 18s (3.4x faster)
Bun: 6s (10x faster)
```
* * *
## [pnpm: The Recommended Default](https://www.pkgpulse.com/blog/pnpm-vs-bun-vs-npm-package-manager-performance-2026\#pnpm-the-recommended-default)
```bash
# Install pnpm:
npm install -g pnpm
# Or via Corepack (Node.js built-in):
corepack enable pnpm
# Common commands:
pnpm install # Install from lockfile
pnpm add react # Add dependency
pnpm add -D typescript # Add dev dependency
pnpm remove lodash # Remove package
pnpm update --interactive # Interactive update UI
pnpm why lodash # Why is this installed?
pnpm ls # List installed packages
```
```yaml
# .npmrc — pnpm configuration:
# Enforce strict peer dependencies:
strict-peer-dependencies=true
# Hoist patterns (allow certain phantom deps for compat):
public-hoist-pattern[]=*eslint*
public-hoist-pattern[]=*prettier*
# Save exact versions:
save-exact=true
# Node linker (for compatibility with some tools):
# node-linker=hoisted # Falls back to npm-style if needed
```
```json
// pnpm-workspace.yaml — monorepo config:
{
"packages": [\
"apps/*",\
"packages/*",\
"tools/*"\
]
}
```
```bash
# pnpm workspace commands:
pnpm --filter web add react-query # Add to specific package
pnpm --filter "!web" install # Install all except web
pnpm -r run build # Run build in all packages
pnpm --filter web... run build # Build web + its dependencies
pnpm --filter ...web run build # Build packages that depend on web
```
### [Why pnpm Over npm](https://www.pkgpulse.com/blog/pnpm-vs-bun-vs-npm-package-manager-performance-2026\#why-pnpm-over-npm)
```
pnpm advantages:
→ No phantom dependencies (package.json must declare everything)
→ 50% less disk usage (hardlinks, not copies)
→ 3-5x faster installs than npm
→ Best workspace support (filtering, recursive)
→ Isolated node_modules (each package sees only its deps)
pnpm limitations:
→ Occasional compatibility issues with poorly-written packages
→ Slightly steeper learning curve for teams migrating from npm
→ Some tools (older ones) expect hoisted node_modules
```
* * *
## [Bun: When Speed Is Everything](https://www.pkgpulse.com/blog/pnpm-vs-bun-vs-npm-package-manager-performance-2026\#bun-when-speed-is-everything)
```bash
# Install Bun:
curl -fsSL https://bun.sh/install | bash
# Bun install commands (compatible with npm syntax):
bun install # Install from lockfile
bun add react # Add dependency
bun add -d typescript # Add dev dependency (note: -d not -D)
bun remove lodash # Remove
bun update # Update all packages
```
```bash
# bun.lock — Bun's lockfile format:
# Binary lockfile (bun.lockb) in older versions
# Text lockfile (bun.lock) in Bun 1.1+
# Commit bun.lock to version control
```
```toml
# bunfig.toml — Bun configuration:
[install]
# Use a private registry:
registry = "https://registry.npmjs.org"
exact = true # Pin exact versions
[install.scopes]
# Scoped registry:
"@mycompany" = { token = "$NPM_TOKEN", url = "https://npm.mycompany.com" }
```
```bash
# Bun workspaces:
# package.json at root:
# {
# "workspaces": ["apps/*", "packages/*"]
# }
bun install # Installs all workspaces
bun add react --workspace apps/web # Add to specific workspace
bun run --filter '*' build # Run build in all workspaces
```
### [Bun Install Limitations](https://www.pkgpulse.com/blog/pnpm-vs-bun-vs-npm-package-manager-performance-2026\#bun-install-limitations)
```
Known compatibility issues in 2026:
→ Some native binaries may not install correctly
→ Postinstall scripts: some packages assume npm/node environment
→ pnpm-specific workspace.yaml not supported (use package.json workspaces)
→ Some packages with complex resolution logic may resolve differently
Test your project before switching to Bun install in CI:
bun install && bun test # Quick compatibility check
```
* * *
## [npm: Universal Compatibility](https://www.pkgpulse.com/blog/pnpm-vs-bun-vs-npm-package-manager-performance-2026\#npm-universal-compatibility)
```bash
# npm — the universal fallback:
npm install # Install
npm install react # Add
npm install -D typescript # Add dev
npm uninstall lodash # Remove
npm update # Update
# npm workspaces (basic):
# package.json: { "workspaces": ["apps/*", "packages/*"] }
npm install # Installs all workspaces
npm run build --workspace=apps/web # Run in specific workspace
npm run build --workspaces # Run in all workspaces
```
* * *
## [Corepack: Managing Package Managers](https://www.pkgpulse.com/blog/pnpm-vs-bun-vs-npm-package-manager-performance-2026\#corepack-managing-package-managers)
```json
// package.json — specify exact package manager:
{
"packageManager": "pnpm@9.15.0"
}
```
```bash
# Enable Corepack (Node.js 16+):
corepack enable
# Now the packageManager field is enforced:
# If you run npm install in a pnpm project, Corepack intercepts:
# "This project requires pnpm@9.15.0. Run 'corepack use pnpm@9.15.0' to switch."
# In CI — enable Corepack before install:
corepack enable
# Then just run: pnpm install (or whatever packageManager specifies)
```
* * *
## [Decision Guide](https://www.pkgpulse.com/blog/pnpm-vs-bun-vs-npm-package-manager-performance-2026\#decision-guide)
```
Use pnpm if:
→ New project, want best practices
→ Monorepo with multiple packages
→ Strict dependency isolation important
→ Most compatible choice that's still fast
Use Bun (install) if:
→ Already using Bun as runtime
→ CI speed is critical and you've tested compatibility
→ Greenfield project with modern packages only
Use npm if:
→ Maximum compatibility needed (legacy projects)
→ Required by tooling that expects npm conventions
→ Team unfamiliar with pnpm/Bun
→ Deploying to environment where only npm is available
```
_Compare package manager downloads on [PkgPulse](https://pkgpulse.com/)._
## Comments
### The 2026 JavaScript Stack Cheatsheet
One PDF: the best package for every category (ORMs, bundlers, auth, testing, state management). Used by 500+ devs. Free, updated monthly.
Get the Free Cheatsheet

View File

@ -1,377 +0,0 @@
[Skip to main content](https://www.pkgpulse.com/blog/react-aria-vs-radix-primitives-2026#main-content)
Accessibility litigation against web applications increased by 30% year-over-year in 2025. Building accessible UI has gone from "nice to have" to legal requirement for many organizations — and the right headless component library can be the difference between compliance and a lawsuit. React Aria (Adobe) and Radix Primitives are the two dominant choices, and they differ profoundly in philosophy.
## [TL;DR](https://www.pkgpulse.com/blog/react-aria-vs-radix-primitives-2026\#tldr)
**Radix Primitives** is the right default for most React applications — pragmatic, well-documented, widely adopted (60K+ stars), and accessible enough for the vast majority of use cases. **React Aria** is the right choice when accessibility is a primary constraint, WCAG compliance is required by contract, or you need the strictest ARIA pattern implementation available. Both are significantly better than building accessible components from scratch.
## [Key Takeaways](https://www.pkgpulse.com/blog/react-aria-vs-radix-primitives-2026\#key-takeaways)
- Radix Primitives: ~3.5M weekly downloads across packages, 60K+ GitHub stars
- React Aria Components: ~260K weekly downloads for `react-aria-components`
- Radix: 28 main components; React Aria: 43+ components
- React Aria is built by Adobe (accessibility specialists), used in Adobe Spectrum
- Radix is the foundation of Shadcn UI — the most popular component library of 2025
- React Aria strictly follows WCAG 2.1 patterns; Radix prioritizes pragmatic DX with good accessibility
- Both ship behavior/logic only — you provide the styles (headless architecture)
## [The Headless Component Model](https://www.pkgpulse.com/blog/react-aria-vs-radix-primitives-2026\#the-headless-component-model)
Both libraries are "headless" — they provide behavior, accessibility, and keyboard interactions, but **no visual styling**. You bring your own CSS (Tailwind, CSS Modules, etc.).
This is the right architecture for component libraries: it separates behavior concerns from visual concerns, making the library usable in any design system.
## [Radix Primitives](https://www.pkgpulse.com/blog/react-aria-vs-radix-primitives-2026\#radix-primitives)
**Packages**: `@radix-ui/react-*` (28+ packages)
**Downloads**: ~3.5M weekly (across all packages)
**GitHub stars**: 16K (primitives repo), 60K+ (Radix UI org)
**Created by**: WorkOS
Radix is the most popular headless component library in the React ecosystem, largely because it powers Shadcn UI.
### [Installation](https://www.pkgpulse.com/blog/react-aria-vs-radix-primitives-2026\#installation)
```bash
# Install individual primitives as needed
npm install @radix-ui/react-dialog
npm install @radix-ui/react-dropdown-menu
npm install @radix-ui/react-select
npm install @radix-ui/react-tooltip
```
### [Usage Pattern](https://www.pkgpulse.com/blog/react-aria-vs-radix-primitives-2026\#usage-pattern)
Radix uses a compound component pattern:
```tsx
import * as Dialog from '@radix-ui/react-dialog';
function DeleteConfirmDialog({ onConfirm }: { onConfirm: () => void }) {
return (
<Dialog.Root>
<Dialog.Trigger asChild>
<button className="btn-danger">Delete Account</button>
</Dialog.Trigger>
<Dialog.Portal>
<Dialog.Overlay className="fixed inset-0 bg-black/50 animate-fade-in" />
<Dialog.Content className="fixed top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 bg-white rounded-lg p-6 shadow-xl">
<Dialog.Title className="text-lg font-semibold">
Confirm Deletion
</Dialog.Title>
<Dialog.Description className="text-gray-600 mt-2">
This action cannot be undone. All your data will be permanently deleted.
</Dialog.Description>
<div className="flex gap-3 mt-6">
<Dialog.Close asChild>
<button className="btn-secondary">Cancel</button>
</Dialog.Close>
<Dialog.Close asChild>
<button className="btn-danger" onClick={onConfirm}>
Delete
</button>
</Dialog.Close>
</div>
</Dialog.Content>
</Dialog.Portal>
</Dialog.Root>
);
}
```
### [`asChild` Prop](https://www.pkgpulse.com/blog/react-aria-vs-radix-primitives-2026\#aschild-prop)
One of Radix's best DX features: `asChild` lets you apply Radix behavior to any component without adding extra DOM elements:
```tsx
import * as Tooltip from '@radix-ui/react-tooltip';
import { Link } from 'next/link'; // Or any custom component
// Without asChild: wraps in a <button>
<Tooltip.Trigger>Hover me</Tooltip.Trigger>
// With asChild: applies trigger behavior to Link directly
<Tooltip.Trigger asChild>
<Link href="/docs">Documentation</Link>
</Tooltip.Trigger>
```
### [Data Attributes for Styling](https://www.pkgpulse.com/blog/react-aria-vs-radix-primitives-2026\#data-attributes-for-styling)
Radix exposes state via `data-` attributes, making CSS targeting clean:
```css
/* Style based on component state */
[data-state="open"] > .trigger-icon {
transform: rotate(180deg);
}
[data-highlighted] {
background-color: var(--color-accent);
}
[data-disabled] {
opacity: 0.5;
cursor: not-allowed;
}
```
### [Available Components (2026)](https://www.pkgpulse.com/blog/react-aria-vs-radix-primitives-2026\#available-components-2026)
Accordion, Alert Dialog, Aspect Ratio, Avatar, Checkbox, Collapsible, Context Menu, Dialog, Dropdown Menu, Form, Hover Card, Label, Menubar, Navigation Menu, Popover, Progress, Radio Group, Scroll Area, Select, Separator, Slider, Switch, Tabs, Toast, Toggle, Toggle Group, Toolbar, Tooltip.
## [React Aria](https://www.pkgpulse.com/blog/react-aria-vs-radix-primitives-2026\#react-aria)
**Package**: `react-aria-components` (all-in-one), or individual `@react-aria/*` hooks
**Downloads**: ~260K weekly (`react-aria-components`)
**GitHub stars**: 13K (react-spectrum monorepo)
**Created by**: Adobe
React Aria comes from Adobe's design systems team — the same team that builds accessibility-focused products used by millions. It implements ARIA patterns from the WAI-ARIA Authoring Practices Guide more strictly than any other library.
### [Installation](https://www.pkgpulse.com/blog/react-aria-vs-radix-primitives-2026\#installation-1)
```bash
# All-in-one package (recommended for new projects)
npm install react-aria-components
# Or individual hooks for granular control
npm install @react-aria/dialog @react-aria/focus @react-stately/dialog
```
### [Component API](https://www.pkgpulse.com/blog/react-aria-vs-radix-primitives-2026\#component-api)
React Aria uses a render props pattern that gives you maximum control:
```tsx
import { Dialog, DialogTrigger, Modal, ModalOverlay, Button, Heading } from 'react-aria-components';
function DeleteConfirmDialog({ onConfirm }: { onConfirm: () => void }) {
return (
<DialogTrigger>
<Button className="btn-danger">Delete Account</Button>
<ModalOverlay className="fixed inset-0 bg-black/50 animate-fade-in">
<Modal className="fixed top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 bg-white rounded-lg p-6 shadow-xl">
<Dialog>
{({ close }) => (
<>
<Heading slot="title" className="text-lg font-semibold">
Confirm Deletion
</Heading>
<p className="text-gray-600 mt-2">
This action cannot be undone.
</p>
<div className="flex gap-3 mt-6">
<Button onPress={close} className="btn-secondary">Cancel</Button>
<Button onPress={() => { onConfirm(); close(); }} className="btn-danger">
Delete
</Button>
</div>
</>
)}
</Dialog>
</Modal>
</ModalOverlay>
</DialogTrigger>
);
}
```
### [CSS Classes with State](https://www.pkgpulse.com/blog/react-aria-vs-radix-primitives-2026\#css-classes-with-state)
React Aria uses a slightly different styling approach:
```tsx
// renderProps pattern for dynamic classes
<Button
className={({ isPressed, isFocused, isDisabled }) =>
`btn ${isPressed ? 'btn-pressed' : ''} ${isFocused ? 'btn-focused' : ''}`
}
>
Click me
</Button>
```
Or with data attributes (similar to Radix):
```css
.btn[data-pressed] { transform: scale(0.98); }
.btn[data-focused] { outline: 2px solid var(--color-focus); }
.btn[data-disabled] { opacity: 0.5; }
```
### [Advanced Accessibility Features](https://www.pkgpulse.com/blog/react-aria-vs-radix-primitives-2026\#advanced-accessibility-features)
React Aria handles edge cases that simpler libraries miss:
```tsx
// ComboBox with proper ARIA pattern
import { ComboBox, Item, Label, Input, Popover, ListBox } from 'react-aria-components';
function SearchComboBox() {
return (
<ComboBox>
<Label>Search countries</Label>
<Input />
<Popover>
<ListBox>
<Item>Afghanistan</Item>
<Item>Albania</Item>
{/* ... */}
</ListBox>
</Popover>
</ComboBox>
);
}
```
React Aria correctly handles:
- Proper `combobox` ARIA role with `aria-expanded`, `aria-haspopup`, `aria-owns`
- `aria-activedescendant` updates on keyboard navigation
- Correct focus management when popup opens/closes
- Mobile: virtual cursor navigation for screen readers on iOS/Android
- Touch: proper pointer events for touch screen accessibility
### [Hook-Level API](https://www.pkgpulse.com/blog/react-aria-vs-radix-primitives-2026\#hook-level-api)
For more control, you can use the individual hooks:
```typescript
import { useButton } from '@react-aria/button';
import { useRef } from 'react';
function CustomButton({ onPress, children }: Props) {
const ref = useRef<HTMLButtonElement>(null);
const { buttonProps } = useButton({ onPress }, ref);
return (
<button
{...buttonProps}
ref={ref}
className="custom-button"
>
{children}
</button>
);
}
```
## [Accessibility Comparison](https://www.pkgpulse.com/blog/react-aria-vs-radix-primitives-2026\#accessibility-comparison)
### [ARIA Compliance](https://www.pkgpulse.com/blog/react-aria-vs-radix-primitives-2026\#aria-compliance)
| Component | Radix | React Aria |
| --- | --- | --- |
| Dialog | WCAG AA | WCAG AAA |
| Select | WCAG AA | WCAG AAA |
| Combobox | Good | Strict |
| Date Picker | Not included | Full ARIA pattern |
| Grid | Not included | Full keyboard nav |
| Virtual cursor | Partial | Full iOS/Android |
### [Screen Reader Testing](https://www.pkgpulse.com/blog/react-aria-vs-radix-primitives-2026\#screen-reader-testing)
React Aria tests against: JAWS + Chrome, NVDA + Firefox, VoiceOver + Safari, TalkBack + Chrome, VoiceOver + iOS Safari.
Radix tests against major screen readers but with less rigor on mobile platforms.
## [Components Available (React Aria vs Radix)](https://www.pkgpulse.com/blog/react-aria-vs-radix-primitives-2026\#components-available-react-aria-vs-radix)
| Component | Radix | React Aria |
| --- | --- | --- |
| Button | No (basic) | Yes |
| Checkbox | Yes | Yes |
| Dialog/Modal | Yes | Yes |
| Dropdown Menu | Yes | Yes |
| Select | Yes | Yes |
| Combobox | No | Yes |
| Date Picker | No | Yes |
| Calendar | No | Yes |
| Color Picker | No | Yes |
| Table/Grid | No | Yes |
| Drag & Drop | No | Yes |
| File Trigger | No | Yes |
| Tag Group | No | Yes |
React Aria has significantly more components, especially for complex interactive patterns.
## [Bundle Size](https://www.pkgpulse.com/blog/react-aria-vs-radix-primitives-2026\#bundle-size)
| Package | Gzipped |
| --- | --- |
| `@radix-ui/react-dialog` | ~5.5 kB |
| `@radix-ui/react-dropdown-menu` | ~12 kB |
| `react-aria-components` (tree-shaken) | ~8-20 kB per component |
## [The Shadcn Effect](https://www.pkgpulse.com/blog/react-aria-vs-radix-primitives-2026\#the-shadcn-effect)
Radix's dominant download numbers are largely the Shadcn UI effect. Shadcn UI is built on Radix Primitives + Tailwind CSS, and it became the most-copied component library of 2025. Every team using Shadcn is installing Radix under the hood:
```bash
# This installs @radix-ui/react-dialog automatically
npx shadcn-ui add dialog
```
There's no Shadcn-equivalent built on React Aria (though Argos CI migrated from Radix to React Aria and published a migration guide).
## [When to Choose Each](https://www.pkgpulse.com/blog/react-aria-vs-radix-primitives-2026\#when-to-choose-each)
**Choose Radix Primitives if:**
- You're using Shadcn UI (Radix is already included)
- You want the largest community and most resources
- "Accessible enough" is acceptable (not strict WCAG AAA)
- Time-to-production is important
- Your design system is already partially built
**Choose React Aria if:**
- WCAG AA/AAA compliance is contractually required
- You need Date Picker, Color Picker, Drag & Drop, or Table with full keyboard navigation
- Your product is used by enterprise customers with accessibility requirements
- You build products used by people with disabilities (government, healthcare, education)
- Mobile screen reader support is critical
## [A Pragmatic Hybrid](https://www.pkgpulse.com/blog/react-aria-vs-radix-primitives-2026\#a-pragmatic-hybrid)
Some teams use Radix for most components and React Aria specifically for the complex ones Radix doesn't handle:
```bash
npm install @radix-ui/react-dialog @radix-ui/react-dropdown-menu # Most UI
npm install react-aria-components # Date pickers, complex patterns
```
This is a valid approach — they're both headless libraries that don't conflict.
## [Compare on PkgPulse](https://www.pkgpulse.com/blog/react-aria-vs-radix-primitives-2026\#compare-on-pkgpulse)
Track download trends for [Radix UI vs React Aria on PkgPulse](https://pkgpulse.com/).
See the live comparison
[View react aria vs. radix on PkgPulse →](https://www.pkgpulse.com/compare/react-aria-vs-radix)
## Comments
giscus
#### 0 reactions
#### 0 comments
WritePreview
[Styling with Markdown is supported](https://guides.github.com/features/mastering-markdown/ "Styling with Markdown is supported")
[Sign in with GitHub](https://giscus.app/api/oauth/authorize?redirect_uri=https%3A%2F%2Fwww.pkgpulse.com%2Fblog%2Freact-aria-vs-radix-primitives-2026)
### The 2026 JavaScript Stack Cheatsheet
One PDF: the best package for every category (ORMs, bundlers, auth, testing, state management). Used by 500+ devs. Free, updated monthly.
Get the Free Cheatsheet

View File

@ -1,372 +0,0 @@
[Skip to main content](https://www.pkgpulse.com/blog/shadcn-ui-vs-base-ui-vs-radix-components-2026#main-content)
The React component ecosystem in 2026 looks nothing like it did three years ago. shadcn/ui went from "interesting experiment" to the default choice for new React projects, collecting 75,000+ GitHub stars in the process. Radix UI — the primitive layer that shadcn originally built on top of — has slowed down since WorkOS acquired it. And Base UI emerged from the MUI team as a serious contender that addresses Radix's architectural shortcomings with production backing from the world's most-downloaded React component library.
The three aren't directly competing. They represent different layers of the component stack, and understanding the relationship between them changes which one you should actually install.
## [TL;DR](https://www.pkgpulse.com/blog/shadcn-ui-vs-base-ui-vs-radix-components-2026\#tldr)
**For most new React projects: shadcn/ui** — it's the industry default for good reason, the February 2026 Visual Builder reduces setup friction to near zero, and it now supports both Radix and Base UI as the underlying primitive layer. **For custom design systems that need unstyled primitives: Base UI** — it's better-maintained than Radix and has cleaner APIs for complex interactions (comboboxes, multi-select, nested menus). **For existing Radix-based projects: keep Radix** unless you have specific pain points — migration isn't worth the disruption for things that work.
## [Key Takeaways](https://www.pkgpulse.com/blog/shadcn-ui-vs-base-ui-vs-radix-components-2026\#key-takeaways)
- **shadcn/ui hit 75,000+ stars** — GitHub's most-starred React UI project for the third consecutive year
- **shadcn/ui now supports Base UI primitives** in addition to Radix — you can choose your primitive layer
- **The February 2026 Visual Builder** lets you configure components visually and copies the exact code — no more manual Tailwind class customization
- **Radix UI was acquired by WorkOS** — updates have slowed, particularly for complex components like Combobox and multi-select
- **Base UI is MUI-backed** with dedicated full-time engineering, not a side project
- **Base UI has better TypeScript types** and cleaner APIs for complex interaction patterns
- **130M monthly npm downloads for Radix** — it's not going anywhere, but active development is slower
- **"Headless UI" is the wrong mental model for shadcn/ui** — it ships with styles, but you own them
## [At a Glance](https://www.pkgpulse.com/blog/shadcn-ui-vs-base-ui-vs-radix-components-2026\#at-a-glance)
| | shadcn/ui | Base UI | Radix UI |
| --- | --- | --- | --- |
| GitHub stars | 75,000+ | 4,200+ | 18,700+ |
| npm downloads/month | ~20M | ~5M | ~130M |
| Styling approach | Tailwind (you own the code) | Unstyled primitives | Unstyled primitives |
| Install model | Copy-paste into your repo | npm package | npm package |
| Backing | Community / Vercel interest | MUI team (full-time) | WorkOS (acquired) |
| TypeScript | ✅ | ✅ Excellent | ✅ Good |
| Accessibility | ✅ (via primitives) | ✅ | ✅ |
| Combobox / multi-select | ✅ (via cmdk) | ✅ Better API | ⚠️ Limited |
| Animation primitives | ✅ | ✅ | ✅ |
| CSS Variables theming | ✅ | ✅ | ✅ |
| Visual Builder | ✅ (Feb 2026) | ❌ | ❌ |
| Bundle size (dialog) | ~8KB | ~6KB | ~9KB |
| React version | 18/19 | 18/19 | 18 (19 in progress) |
## [What Each Actually Is](https://www.pkgpulse.com/blog/shadcn-ui-vs-base-ui-vs-radix-components-2026\#what-each-actually-is)
Understanding these three tools requires being clear about what layer they occupy:
**Radix UI** and **Base UI** are _headless component primitives_ — they handle accessibility, keyboard navigation, ARIA attributes, and interaction logic with zero styling. You get behavior, not appearance. You add the CSS.
**shadcn/ui** is _not a component library in the npm sense_. It's a collection of pre-built components using Tailwind CSS, built on top of headless primitives (originally Radix, now Base UI too). When you run `npx shadcn@latest add button`, it copies the source code for a Button component into your project at `components/ui/button.tsx`. You then own that code — you can modify it however you want. There's no package to update.
This distinction matters for how you evaluate them:
```bash
# Radix/Base UI — installed as a dependency, updated via npm
npm install @radix-ui/react-dialog
npm install @base-ui-components/react
# shadcn/ui — components copied into your project
npx shadcn@latest add dialog
# Creates: components/ui/dialog.tsx
# You own this file. Edit it freely.
```
## [shadcn/ui: The Copy-Paste Revolution](https://www.pkgpulse.com/blog/shadcn-ui-vs-base-ui-vs-radix-components-2026\#shadcnui-the-copy-paste-revolution)
The "install npm package vs copy into your project" distinction sounds like a minor implementation detail. In practice, it changes the entire maintenance model:
```tsx
// After running: npx shadcn@latest add dialog
// This file is in YOUR repo at components/ui/dialog.tsx
"use client"
import * as React from "react"
import * as DialogPrimitive from "@radix-ui/react-dialog"
import { X } from "lucide-react"
import { cn } from "@/lib/utils"
const Dialog = DialogPrimitive.Root
const DialogTrigger = DialogPrimitive.Trigger
const DialogPortal = DialogPrimitive.Portal
const DialogOverlay = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Overlay>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay>
>(({ className, ...props }, ref) => (
<DialogPrimitive.Overlay
ref={ref}
className={cn(
"fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
className
)}
{...props}
/>
))
```
You own this code. You see every className. You can change the animation, the overlay color, the z-index, the transition timing — all without fighting a library's theming system. Want a drawer instead of a centered modal? Edit the classes.
This is fundamentally different from installing MUI or Mantine, where customizing deeply means fighting overrides.
## [The February 2026 Visual Builder](https://www.pkgpulse.com/blog/shadcn-ui-vs-base-ui-vs-radix-components-2026\#the-february-2026-visual-builder)
shadcn's biggest 2026 addition is the Visual Builder — an interactive configurator that lets you adjust variants, sizes, and appearances visually, then copies the exact Tailwind code for your specific configuration:
```bash
# No install needed — open the builder at:
# https://ui.shadcn.com/builder
# It generates the exact component code for your choices:
# - Variant (default/destructive/outline/secondary/ghost/link)
# - Size (default/sm/lg/icon)
# - Custom colors/spacing
# Then copy the output to your components/ui/button.tsx
```
For teams that struggled with "what Tailwind classes produce the exact style I want," this removes the main friction point. You configure visually, get the code, own the result.
## [Switching shadcn/ui from Radix to Base UI](https://www.pkgpulse.com/blog/shadcn-ui-vs-base-ui-vs-radix-components-2026\#switching-shadcnui-from-radix-to-base-ui)
The February 2026 update that went somewhat under the radar: shadcn/ui now officially supports Base UI as the underlying primitive layer, in addition to Radix:
```bash
# Initialize shadcn with Base UI primitives (new in 2026)
npx shadcn@latest init --base-ui
# Or add individual components using Base UI
npx shadcn@latest add dialog --primitive=base-ui
```
The resulting components look identical to the user, but the underlying primitive has better TypeScript types and a more consistent API. For new projects, this is worth choosing. For existing Radix-based shadcn projects, migration isn't required.
## [Base UI: MUI's Headless Bet](https://www.pkgpulse.com/blog/shadcn-ui-vs-base-ui-vs-radix-components-2026\#base-ui-muis-headless-bet)
Base UI is the MUI (Material UI) team's headless component library, extracted from their earlier "Unstyled components" offering and rebuilt from the ground up. The key difference from Radix:
**Full-time engineering backing.** Radix is maintained by the WorkOS team with a small core team. Base UI has dedicated MUI engineers working on it as a primary product investment. MUI serves millions of developers — they have strong incentives to keep Base UI production-quality.
```tsx
// Base UI Dialog — cleaner API surface
import * as Dialog from "@base-ui-components/react/dialog";
function ConfirmDialog({ open, onOpenChange, onConfirm }) {
return (
<Dialog.Root open={open} onOpenChange={onOpenChange}>
<Dialog.Portal>
<Dialog.Backdrop className="dialog-backdrop" />
<Dialog.Popup className="dialog-popup">
<Dialog.Title>Confirm Action</Dialog.Title>
<Dialog.Description>
This action cannot be undone.
</Dialog.Description>
<div className="dialog-actions">
<Dialog.Close>Cancel</Dialog.Close>
<button onClick={onConfirm}>Confirm</button>
</div>
</Dialog.Popup>
</Dialog.Portal>
</Dialog.Root>
);
}
```
The naming is cleaner: `Dialog.Popup` instead of `DialogContent`, `Dialog.Backdrop` instead of `DialogOverlay`. Minor difference in isolation, but consistent across all components.
### [Base UI's Combobox: Where It Genuinely Wins](https://www.pkgpulse.com/blog/shadcn-ui-vs-base-ui-vs-radix-components-2026\#base-uis-combobox-where-it-genuinely-wins)
Radix has been criticized for weak support for complex interaction patterns, particularly comboboxes and multi-select. The Radix Select primitive is intentionally limited, and building a proper searchable multi-select on top of it requires significant custom code:
```tsx
// Radix approach to searchable select — requires cmdk workaround
import * as Select from "@radix-ui/react-select";
// Radix Select doesn't support search natively
// shadcn/ui uses cmdk (Command) for this pattern
// Base UI Combobox — built-in search support
import * as Combobox from "@base-ui-components/react/combobox";
function TagSelector({ tags, onSelect }) {
return (
<Combobox.Root multiple>
<Combobox.Input placeholder="Search tags..." />
<Combobox.Popup>
{tags.map(tag => (
<Combobox.Item key={tag.id} value={tag.id}>
<Combobox.ItemText>{tag.name}</Combobox.ItemText>
<Combobox.ItemIndicator></Combobox.ItemIndicator>
</Combobox.Item>
))}
</Combobox.Popup>
</Combobox.Root>
);
}
// Multi-select combobox with search, built natively
```
If your application needs complex form interactions — multi-select dropdowns, searchable selects, tag inputs — Base UI's primitive set handles them more naturally than Radix.
## [Radix UI: The Market Leader with a Question Mark](https://www.pkgpulse.com/blog/shadcn-ui-vs-base-ui-vs-radix-components-2026\#radix-ui-the-market-leader-with-a-question-mark)
With 130M monthly downloads, Radix UI is the most widely used headless component library in the React ecosystem. It's the primitive layer under shadcn/ui (in its default configuration), Mantine UI, and dozens of other component libraries.
```tsx
// Radix — the primitives you're probably already using
import * as Dialog from "@radix-ui/react-dialog";
import * as Select from "@radix-ui/react-select";
import * as Tooltip from "@radix-ui/react-tooltip";
import * as DropdownMenu from "@radix-ui/react-dropdown-menu";
// Fully accessible, keyboard navigable, WAI-ARIA compliant
// Every property is documented, every pattern tested
```
The concern isn't whether Radix is good — it is good. The concern is trajectory. After WorkOS acquired Radix, the update cadence slowed. Several long-standing issues (combobox support, React 19 compatibility updates) moved slowly through the pipeline. The core team is smaller than Base UI's now.
For projects already built on Radix: don't migrate. The primitives work, the ecosystem is massive, and the API won't break overnight. For new projects choosing between Radix and Base UI as a primitive foundation: Base UI has momentum on its side.
### [React 19 Compatibility](https://www.pkgpulse.com/blog/shadcn-ui-vs-base-ui-vs-radix-components-2026\#react-19-compatibility)
```tsx
// Radix — React 19 compatibility update was delayed
// Some @radix-ui/* packages needed updates for React 19's new ref handling
// Most updated by late 2025, but caused upgrade friction
// Base UI — built with React 19 in mind from the start
// No compatibility issues with the new ref transformation
```
Teams upgrading to React 19 encountered some rough edges with Radix packages that took months to fully resolve. Base UI was designed after React 19's changes were known and avoids these patterns.
## [Bundle Size Reality](https://www.pkgpulse.com/blog/shadcn-ui-vs-base-ui-vs-radix-components-2026\#bundle-size-reality)
```
Component: Dialog (open/close with animation)
Radix @radix-ui/react-dialog:
Package size: 9.2KB (gzipped)
Dependencies: @radix-ui/react-portal, @radix-ui/primitive, etc.
Total with deps: ~28KB
Base UI @base-ui-components/react (dialog only):
Package size: 6.4KB (gzipped)
Self-contained: yes (fewer cross-package deps)
Total: ~18KB
shadcn/ui Dialog (your compiled code + Radix):
Component code: ~3KB (Tailwind, compiled)
Runtime: Radix primitives (see above)
Total: ~31KB but includes styles
```
The numbers are close enough that bundle size shouldn't drive your decision. What matters more: Radix has many cross-package dependencies (each primitive is a separate package), which can inflate your `node_modules` significantly on larger projects. Base UI is more self-contained.
## [TypeScript Integration](https://www.pkgpulse.com/blog/shadcn-ui-vs-base-ui-vs-radix-components-2026\#typescript-integration)
```tsx
// Radix — solid TypeScript, component-level types
import * as Dialog from "@radix-ui/react-dialog";
// The types are there but sometimes overly broad
type DialogContentProps = React.ComponentPropsWithoutRef<
typeof Dialog.Content
>; // Works but verbose
// Base UI — excellent TypeScript, consistent naming
import * as Dialog from "@base-ui-components/react/dialog";
// Sub-path imports give you tree-shaking + precise types
type DialogPopupProps = React.ComponentPropsWithoutRef<
typeof Dialog.Popup
>;
// Base UI components consistently expose:
// - render prop for custom element types
// - className string or function receiving state
// - All HTML attributes via ...props
function StyledDialog({ open }: { open: boolean }) {
return (
<Dialog.Popup
// className can be a function receiving component state
className={(state) =>
state.open ? "dialog-open" : "dialog-closed"
}
/>
);
}
```
Base UI's state-based className function is a pattern that makes conditional styling clean without needing `data-[state=open]:` Tailwind selectors.
## [Choosing Your Stack in 2026](https://www.pkgpulse.com/blog/shadcn-ui-vs-base-ui-vs-radix-components-2026\#choosing-your-stack-in-2026)
**Recommendation for new Next.js/React projects:**
```bash
# 1. Install shadcn/ui with Base UI primitives (best of both worlds)
npx shadcn@latest init --base-ui
# 2. Add components as you need them
npx shadcn@latest add button dialog dropdown-menu tooltip
# 3. Customize the generated code in components/ui/
# — You own it. Tailwind classes are yours to change.
```
This gives you: shadcn's pre-built components and Visual Builder, Base UI's better primitives under the hood, and ownership of all the code.
**For custom design systems (no shadcn):**
```bash
# Base UI — unstyled primitives with modern API
npm install @base-ui-components/react
# Use CSS modules, CSS-in-JS, or Tailwind — your choice
# Better comboboxes, cleaner types, active development
```
**For existing Radix-based projects:**
```bash
# Don't migrate unless you have specific pain points.
# Radix works. 130M downloads/month says so.
# Wait for shadcn/ui's migration tooling if you ever want to switch.
```
## [The Three-Layer Mental Model](https://www.pkgpulse.com/blog/shadcn-ui-vs-base-ui-vs-radix-components-2026\#the-three-layer-mental-model)
The cleanest mental model for understanding these three:
```
Layer 3: shadcn/ui
↓ Pre-built, Tailwind-styled, copy-pasted into your project
↓ Uses Layer 2 as primitives (configurable: Radix or Base UI)
Layer 2: Radix UI / Base UI
↓ Unstyled, accessible, behavior-only
↓ You provide all styling
↓ Both implement ARIA patterns correctly
Layer 1: Your application
↓ Uses whichever combination serves your needs
```
Most developers don't need to choose between all three. They choose shadcn/ui (which bundles the decision about Layer 2) or they choose a headless primitive directly (Radix or Base UI) for a custom design system. The 2026 update that shadcn/ui supports both Radix and Base UI as primitive layers means you're no longer locked in.
* * *
_Compare shadcn/ui, Base UI, and Radix UI download trends at [PkgPulse](https://www.pkgpulse.com/compare/shadcn-ui-vs-radix-ui)._
_Related: [React 19 vs React 18 2026](https://www.pkgpulse.com/blog/react-19-vs-react-18-2026) · [Tailwind CSS vs CSS Modules 2026](https://www.pkgpulse.com/blog/tailwind-vs-css-modules-2026) · [MUI vs Ant Design 2026](https://www.pkgpulse.com/blog/mui-vs-ant-design-2026)_
See the live comparison
[View shadcn ui vs. radix ui on PkgPulse →](https://www.pkgpulse.com/compare/shadcn-ui-vs-radix-ui)
## Comments
giscus
#### 0 reactions
#### 0 comments
WritePreview
[Styling with Markdown is supported](https://guides.github.com/features/mastering-markdown/ "Styling with Markdown is supported")
[Sign in with GitHub](https://giscus.app/api/oauth/authorize?redirect_uri=https%3A%2F%2Fwww.pkgpulse.com%2Fblog%2Fshadcn-ui-vs-base-ui-vs-radix-components-2026)
### The 2026 JavaScript Stack Cheatsheet
One PDF: the best package for every category (ORMs, bundlers, auth, testing, state management). Used by 500+ devs. Free, updated monthly.
Get the Free Cheatsheet

View File

@ -1,302 +0,0 @@
[Skip to main content](https://www.pkgpulse.com/blog/vitest-jest-playwright-complete-testing-stack-2026#main-content)
## [TL;DR](https://www.pkgpulse.com/blog/vitest-jest-playwright-complete-testing-stack-2026\#tldr)
**The modern testing stack: Vitest (unit/integration) + Playwright (E2E).** Jest is still running millions of tests across the npm ecosystem, but new projects default to Vitest because it shares Vite's config, runs tests in parallel by default, and is 5-10x faster. Playwright replaced Cypress as the E2E tool of choice — better multi-tab support, less flakiness, and first-class TypeScript. The old stack (Jest + Enzyme/React Testing Library + Cypress) still works, but the new stack (Vitest + Testing Library + Playwright) is faster, simpler, and better.
## [Key Takeaways](https://www.pkgpulse.com/blog/vitest-jest-playwright-complete-testing-stack-2026\#key-takeaways)
- **Vitest**: Jest-compatible API, Vite-native, ~10x faster, TypeScript without setup
- **Jest**: 40M+ weekly downloads (legacy), still excellent, no reason to migrate working tests
- **Playwright**: multi-browser E2E, trace viewer, 80%+ market share in new projects
- **Cypress**: real-time browser view is great DX but slower and less capable than Playwright
- **Testing Library**: the default React component testing approach — framework-agnostic
* * *
## [Unit Testing: Vitest vs Jest](https://www.pkgpulse.com/blog/vitest-jest-playwright-complete-testing-stack-2026\#unit-testing-vitest-vs-jest)
```typescript
// The APIs are nearly identical — migration is usually find-and-replace:
// ─── Jest ───
// jest.config.js
module.exports = {
transform: { '^.+\\.tsx?$': ['ts-jest', {}] }, // setup required
testEnvironment: 'jsdom',
};
// test file:
import { sum } from './math';
describe('math utils', () => {
test('adds two numbers', () => {
expect(sum(1, 2)).toBe(3);
});
it('handles negatives', () => {
expect(sum(-1, -2)).toBe(-3);
});
});
// ─── Vitest ───
// vite.config.ts (reuses existing Vite config!)
import { defineConfig } from 'vite';
export default defineConfig({
test: {
environment: 'jsdom',
globals: true, // optional: makes describe/test/expect global without import
},
});
// test file — identical to Jest:
import { sum } from './math';
describe('math utils', () => {
test('adds two numbers', () => {
expect(sum(1, 2)).toBe(3);
});
});
// Performance comparison (500 unit tests, React project):
// Jest (with ts-jest): 8.4s
// Jest (with babel-jest): 11.2s
// Vitest: 1.8s 🏆
// Why Vitest is faster:
// → Uses esbuild for transforms (same as Vite dev server)
// → Parallel by default (worker threads, one per test file)
// → No separate config for TS — shares Vite's esbuild config
// → Module resolution uses Vite's resolver (no duplicate setup)
```
* * *
## [Component Testing with React Testing Library](https://www.pkgpulse.com/blog/vitest-jest-playwright-complete-testing-stack-2026\#component-testing-with-react-testing-library)
```typescript
// Testing Library works with both Jest and Vitest — same API:
// Setup (Vitest):
// package.json:
{
"test": "vitest",
"dependencies": {
"@testing-library/react": "^15",
"@testing-library/user-event": "^14",
"@testing-library/jest-dom": "^6"
}
}
// vitest.setup.ts:
import '@testing-library/jest-dom/vitest'; // extends expect with toBeInDocument etc.
// vite.config.ts:
export default defineConfig({
test: {
environment: 'jsdom',
setupFiles: ['./vitest.setup.ts'],
},
});
// Component test:
import { render, screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { LoginForm } from './LoginForm';
describe('LoginForm', () => {
it('submits with email and password', async () => {
const mockSubmit = vi.fn(); // vi.fn() instead of jest.fn()
render(<LoginForm onSubmit={mockSubmit} />);
await userEvent.type(screen.getByLabelText('Email'), 'user@example.com');
await userEvent.type(screen.getByLabelText('Password'), 'password123');
await userEvent.click(screen.getByRole('button', { name: /sign in/i }));
await waitFor(() => {
expect(mockSubmit).toHaveBeenCalledWith({
email: 'user@example.com',
password: 'password123',
});
});
});
it('shows error for invalid email', async () => {
render(<LoginForm onSubmit={vi.fn()} />);
await userEvent.type(screen.getByLabelText('Email'), 'not-an-email');
await userEvent.click(screen.getByRole('button', { name: /sign in/i }));
expect(screen.getByText(/invalid email/i)).toBeInTheDocument();
});
});
```
* * *
## [E2E Testing: Playwright vs Cypress](https://www.pkgpulse.com/blog/vitest-jest-playwright-complete-testing-stack-2026\#e2e-testing-playwright-vs-cypress)
```typescript
// ─── Playwright ───
// playwright.config.ts
import { defineConfig } from '@playwright/test';
export default defineConfig({
testDir: './e2e',
use: {
baseURL: 'http://localhost:3000',
trace: 'on-first-retry', // Capture traces on failure
},
projects: [\
{ name: 'chromium', use: { browserName: 'chromium' } },\
{ name: 'firefox', use: { browserName: 'firefox' } },\
{ name: 'safari', use: { browserName: 'webkit' } },\
{ name: 'mobile', use: { ...devices['iPhone 13'] } },\
],
});
// e2e/auth.spec.ts
import { test, expect } from '@playwright/test';
test('user can log in', async ({ page }) => {
await page.goto('/login');
await page.fill('[data-testid="email"]', 'user@example.com');
await page.fill('[data-testid="password"]', 'password123');
await page.click('button[type="submit"]');
await expect(page).toHaveURL('/dashboard');
await expect(page.locator('h1')).toHaveText('Welcome back');
});
// Multi-tab test (Playwright exclusive):
test('cart persists across tabs', async ({ context }) => {
const page1 = await context.newPage();
const page2 = await context.newPage();
await page1.goto('/product/1');
await page1.click('button:text("Add to Cart")');
await page2.goto('/cart');
await expect(page2.locator('.cart-item')).toHaveCount(1);
});
// API mocking in tests:
test('shows error when API fails', async ({ page }) => {
await page.route('**/api/users', route => route.fulfill({
status: 500,
body: JSON.stringify({ error: 'Server error' }),
}));
await page.goto('/users');
await expect(page.locator('.error-message')).toBeVisible();
});
```
* * *
## [Playwright Trace Viewer: Debugging E2E Failures](https://www.pkgpulse.com/blog/vitest-jest-playwright-complete-testing-stack-2026\#playwright-trace-viewer-debugging-e2e-failures)
```bash
# Run tests with trace on failure:
npx playwright test --trace on
# Or configure in playwright.config.ts:
use: { trace: 'on-first-retry' }
# After a failure, view the trace:
npx playwright show-trace test-results/trace.zip
# The trace viewer shows:
# → Screenshot at each action
# → Network requests and responses
# → Console logs and errors
# → DOM snapshots you can inspect
# → Timeline of the test execution
# This replaces hours of debugging with 5 minutes of trace review
# Run specific test in headed mode (see the browser):
npx playwright test --headed auth.spec.ts
# Generate test code by recording browser actions:
npx playwright codegen http://localhost:3000
# → Opens browser, records your clicks, generates test code
```
* * *
## [Complete Testing Stack Setup](https://www.pkgpulse.com/blog/vitest-jest-playwright-complete-testing-stack-2026\#complete-testing-stack-setup)
```bash
# Install everything for the modern stack:
npm install --save-dev \
vitest \
@vitest/ui \ # visual test runner UI
jsdom \ # browser environment for unit tests
@testing-library/react \
@testing-library/user-event \
@testing-library/jest-dom \
@playwright/test
# Install Playwright browsers (one-time):
npx playwright install
# package.json scripts:
{
"scripts": {
"test": "vitest",
"test:ui": "vitest --ui",
"test:watch": "vitest --watch",
"test:e2e": "playwright test",
"test:e2e:ui": "playwright test --ui",
"test:all": "vitest run && playwright test"
}
}
# File structure:
src/
components/
Button/
Button.tsx
Button.test.tsx ← unit/integration test (Vitest + Testing Library)
utils/
math.test.ts ← unit test
e2e/
auth.spec.ts ← E2E test (Playwright)
checkout.spec.ts
playwright.config.ts
vite.config.ts ← Vitest config lives here
```
* * *
## [When to Keep Jest](https://www.pkgpulse.com/blog/vitest-jest-playwright-complete-testing-stack-2026\#when-to-keep-jest)
```
Keep Jest when:
→ Existing test suite works — don't migrate for the sake of it
→ Your project doesn't use Vite (Create React App, custom Webpack setup)
→ You use Jest-specific features (jest.spyOn, jest.useFakeTimers) extensively
→ Your team knows Jest deeply and migration would cause disruption
Migrate to Vitest when:
→ New project (always use Vitest)
→ Test suite is slow and painful (10+ second runs for unit tests)
→ You've already migrated to Vite for bundling
→ TypeScript setup with ts-jest is causing friction
Migration process (from Jest to Vitest):
1. npx vitest-migration # automated codemods available
2. Replace jest.fn() → vi.fn()
3. Replace jest.mock() → vi.mock()
4. Update jest.config.js → vitest config in vite.config.ts
5. Run tests: expect ~95% to pass without changes
The compatibility is excellent — most Jest tests run on Vitest unchanged.
```
* * *
_Compare Vitest, Jest, Playwright, and other testing library trends at [PkgPulse](https://www.pkgpulse.com/compare/vitest-vs-jest)._
See the live comparison
[View vitest vs. jest on PkgPulse →](https://www.pkgpulse.com/compare/vitest-vs-jest)
## Comments
### The 2026 JavaScript Stack Cheatsheet
One PDF: the best package for every category (ORMs, bundlers, auth, testing, state management). Used by 500+ devs. Free, updated monthly.
Get the Free Cheatsheet

View File

@ -1,290 +0,0 @@
[Skip to main content](https://www.pkgpulse.com/blog/zod-v4-vs-arktype-vs-typebox-vs-valibot-schema-2026#main-content)
## [TL;DR](https://www.pkgpulse.com/blog/zod-v4-vs-arktype-vs-typebox-vs-valibot-schema-2026\#tldr)
**Zod v4 remains the default for TypeScript validation — but Valibot (8KB vs Zod's 60KB) and ArkType (fastest runtime parsing) are compelling for performance-critical use cases.** TypeBox generates JSON Schema natively, making it the best choice for OpenAPI/Swagger integration. For new projects: Zod v4. For edge/bundle-size-critical: Valibot. For OpenAPI: TypeBox. For maximum runtime speed: ArkType.
## [Key Takeaways](https://www.pkgpulse.com/blog/zod-v4-vs-arktype-vs-typebox-vs-valibot-schema-2026\#key-takeaways)
- **Zod v4**: 60KB, 10M+ downloads/week, best ecosystem (react-hook-form, trpc, drizzle)
- **Valibot**: 8KB, tree-shakable, modular API, ~10x smaller than Zod
- **ArkType**: Fastest parser (3-10x faster than Zod), TypeScript syntax strings
- **TypeBox**: JSON Schema native, `Static<typeof Schema>` TypeScript types
- **Performance**: ArkType > Valibot > TypeBox > Zod (but all are "fast enough" for most apps)
- **Ecosystem**: Zod integrates with everything; others are catching up
* * *
## [Downloads](https://www.pkgpulse.com/blog/zod-v4-vs-arktype-vs-typebox-vs-valibot-schema-2026\#downloads)
| Package | Weekly Downloads | Trend |
| --- | --- | --- |
| `zod` | ~10M | ↑ Growing |
| `@sinclair/typebox` | ~6M | ↑ Growing |
| `valibot` | ~1M | ↑ Fast growing |
| `arktype` | ~200K | ↑ Growing |
* * *
## [Performance Benchmarks](https://www.pkgpulse.com/blog/zod-v4-vs-arktype-vs-typebox-vs-valibot-schema-2026\#performance-benchmarks)
```
Schema: User object with 10 fields, nested address, array of tags
Parsing 100,000 objects:
ArkType: 45ms ← Fastest
Valibot: 120ms
TypeBox: 180ms
Zod v4: 280ms (v4 is 2x faster than v3's ~580ms)
Bundle size (minified + gzipped):
Valibot: 8KB ← Smallest
ArkType: 12KB
TypeBox: 60KB (includes JSON Schema types)
Zod v4: 60KB
Type inference speed (tsc, 50-field schema):
ArkType: ~200ms
Zod v4: ~450ms
Valibot: ~600ms
TypeBox: ~300ms
```
* * *
## [Zod v4: The Default](https://www.pkgpulse.com/blog/zod-v4-vs-arktype-vs-typebox-vs-valibot-schema-2026\#zod-v4-the-default)
```typescript
// Zod v4 — new features and performance improvements:
import { z } from 'zod';
// Basic schema (same as v3):
const UserSchema = z.object({
id: z.string().cuid2(),
email: z.string().email(),
name: z.string().min(2).max(100),
age: z.number().int().min(0).max(150).optional(),
role: z.enum(['user', 'admin', 'moderator']),
tags: z.array(z.string()).max(10),
address: z.object({
street: z.string(),
city: z.string(),
country: z.string().length(2), // ISO 2-letter
}).optional(),
metadata: z.record(z.string(), z.unknown()),
createdAt: z.coerce.date(), // Auto-coerce string → Date
});
type User = z.infer<typeof UserSchema>;
// Zod v4 new: z.file() for Blob/File
const UploadSchema = z.object({
file: z.instanceof(File)
.refine(f => f.size < 5_000_000, 'Max 5MB')
.refine(f => ['image/jpeg', 'image/png', 'image/webp'].includes(f.type), 'Must be JPEG/PNG/WebP'),
caption: z.string().max(500).optional(),
});
// Zod v4 new: z.pipe() for chained transforms
const ParsedDateSchema = z
.string()
.pipe(z.coerce.date()); // string → validated Date
// Zod v4 new: z.toJSONSchema()
const jsonSchema = z.toJSONSchema(UserSchema);
// Generates standard JSON Schema — useful for OpenAPI docs
// Error formatting (v4 — cleaner):
const result = UserSchema.safeParse({ email: 'bad' });
if (!result.success) {
const errors = result.error.flatten();
// { fieldErrors: { email: ['Invalid email'] }, formErrors: [] }
}
```
* * *
## [Valibot: Bundle-Size Champion](https://www.pkgpulse.com/blog/zod-v4-vs-arktype-vs-typebox-vs-valibot-schema-2026\#valibot-bundle-size-champion)
```typescript
// Valibot — modular, tree-shakable:
import {
object, string, number, array, optional, enum_,
email, minLength, maxLength, integer, minValue, maxValue,
parse, safeParse, flatten,
type InferInput, type InferOutput,
} from 'valibot';
// Only imports what you use — tree-shaking reduces bundle to ~2-5KB for simple schemas
const UserSchema = object({
id: string([minLength(1)]),
email: string([email()]),
name: string([minLength(2), maxLength(100)]),
age: optional(number([integer(), minValue(0), maxValue(150)])),
role: enum_(['user', 'admin', 'moderator']),
tags: array(string(), [maxLength(10)]),
});
type User = InferInput<typeof UserSchema>;
// Parse (throws on error):
const user = parse(UserSchema, rawData);
// Safe parse (returns result/error):
const result = safeParse(UserSchema, rawData);
if (result.success) {
console.log(result.output);
} else {
const errors = flatten(result.issues);
// { nested: { email: ['Invalid email'] } }
}
```
```typescript
// Valibot with React Hook Form:
import { valibotResolver } from '@hookform/resolvers/valibot';
import { useForm } from 'react-hook-form';
function SignupForm() {
const { register, handleSubmit, formState: { errors } } = useForm({
resolver: valibotResolver(UserSchema),
});
return (
<form onSubmit={handleSubmit(console.log)}>
<input {...register('email')} />
{errors.email && <span>{errors.email.message}</span>}
</form>
);
}
```
* * *
## [ArkType: Fastest Runtime + TypeScript Syntax](https://www.pkgpulse.com/blog/zod-v4-vs-arktype-vs-typebox-vs-valibot-schema-2026\#arktype-fastest-runtime--typescript-syntax)
```typescript
// ArkType — TypeScript-syntax strings for schemas:
import { type } from 'arktype';
// Syntax feels like writing TypeScript:
const User = type({
id: 'string',
email: 'string.email',
name: '2 <= string <= 100', // min/max length shorthand!
age: 'number.integer | undefined',
role: '"user" | "admin" | "moderator"',
tags: 'string[] <= 10', // array with max length
createdAt: 'Date',
});
type User = typeof User.infer;
// Parse:
const result = User(rawData);
// ArkType returns morph (with parse) or error:
if (result instanceof type.errors) {
console.log(result.summary); // Human-readable error
} else {
// result is User
}
// ArkType advanced: morphs (transform)
const ParsedDate = type('string').pipe(s => new Date(s), 'Date');
// Recursive types (Zod struggles here):
const TreeNode = type({
value: 'number',
children: 'TreeNode[]', // Self-referencing!
}).describe('TreeNode'); // Named for error messages
```
* * *
## [TypeBox: JSON Schema Native](https://www.pkgpulse.com/blog/zod-v4-vs-arktype-vs-typebox-vs-valibot-schema-2026\#typebox-json-schema-native)
```typescript
// TypeBox — generates JSON Schema, used in Fastify/Hono:
import { Type, Static } from '@sinclair/typebox';
import { Value } from '@sinclair/typebox/value';
// TypeBox schema IS JSON Schema:
const UserSchema = Type.Object({
id: Type.String({ format: 'uuid' }),
email: Type.String({ format: 'email' }),
name: Type.String({ minLength: 2, maxLength: 100 }),
age: Type.Optional(Type.Integer({ minimum: 0, maximum: 150 })),
role: Type.Union([\
Type.Literal('user'),\
Type.Literal('admin'),\
Type.Literal('moderator'),\
]),
tags: Type.Array(Type.String(), { maxItems: 10 }),
});
// TypeScript type from schema:
type User = Static<typeof UserSchema>;
// Validate:
const result = Value.Check(UserSchema, rawData);
if (!result) {
const errors = [...Value.Errors(UserSchema, rawData)];
// [{ path: '/email', message: 'Expected string' }]
}
// TypeBox + Hono (validated routes with OpenAPI):
import { Hono } from 'hono';
import { describeRoute } from 'hono-openapi';
const app = new Hono();
app.post('/users',
describeRoute({
requestBody: { content: { 'application/json': { schema: UserSchema } } },
responses: { 201: { description: 'Created' } },
}),
async (c) => { /* handler */ }
);
// Export OpenAPI spec:
// app.doc('/openapi.json', { openapi: '3.0.0', info: { title: 'API', version: '1' } })
```
* * *
## [Decision Guide](https://www.pkgpulse.com/blog/zod-v4-vs-arktype-vs-typebox-vs-valibot-schema-2026\#decision-guide)
```
Use Zod v4 if:
→ Default choice — best ecosystem (react-hook-form, trpc, drizzle, next-safe-action)
→ Team already knows Zod v3 (v4 is mostly backwards compatible)
→ Need broad library compatibility
→ Bundle size is not a constraint
Use Valibot if:
→ Edge runtime / bundle size critical (<5KB budget)
→ Want tree-shakable, pay-only-for-what-you-use
→ Cloudflare Workers or similar constrained environments
Use ArkType if:
→ Parsing millions of objects (backend hot path)
→ Love TypeScript-native syntax strings
→ Need recursive types easily
→ Fastest possible validation
Use TypeBox if:
→ Building OpenAPI/Swagger documentation
→ Using Fastify (TypeBox is Fastify's native schema)
→ Need JSON Schema output for other tools
→ API validation that also generates docs
```
_Compare Zod, Valibot, ArkType, and TypeBox on [PkgPulse](https://pkgpulse.com/compare/zod-vs-valibot)._
## Comments
### The 2026 JavaScript Stack Cheatsheet
One PDF: the best package for every category (ORMs, bundlers, auth, testing, state management). Used by 500+ devs. Free, updated monthly.
Get the Free Cheatsheet

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,252 +0,0 @@
## TL;DR
Claude Code guesses your component APIs, theme tokens, and project setup — and gets them wrong half the time. shadcn/ui ships 3 tools (Skills, MCP Server, Preset) that inject real project context into your AI. Setup takes 5 minutes, 3 commands.
```
# 1. Project context
npx skills add shadcn/ui
# 2. Live docs
claude mcp add shadcn -- npx shadcn@latest mcp
# 3. Design system
npx shadcn@latest init --preset a1Dg5eFl
```
Enter fullscreen modeExit fullscreen mode
* * *
## The Problem: AI-Generated UI Looks Inconsistent
If you've used Claude Code to build UI, you've probably seen this:
- The AI generates a `<Button variant="outline">` but your project uses `<Button variant="ghost">`.
- Colors change between pages because there's no shared design token.
- You spend 15 minutes searching docs for the correct props, then paste them into the prompt.
This happens because Claude Code doesn't know your project context. It relies on training data — not your `components.json`, not your Tailwind config, not your installed component list.
## What is shadcn/ui?
Quick background if you haven't used it: **shadcn/ui** is a copy-and-own component system built on Radix UI and Tailwind CSS. Instead of installing an npm package, you copy component source code directly into your project. Full ownership, full customization.
86,900+ GitHub stars. Used by Adobe, OpenAI, Sonos. 70+ components — Button, Card, Dialog, Data Table, Sidebar, Chart, and more.
## The 3 Settings That Fix Everything
shadcn/ui CLI v4 (released March 2026) has a tagline: **"Built for you and your coding agents."** Here's what that means in practice.
### 1\. Skills: Inject Project Context
```
npx skills add shadcn/ui
```
Enter fullscreen modeExit fullscreen mode
This one command makes Claude Code "understand" your project. Skills detects your `components.json` and runs `shadcn info --json` to feed the AI:
- Framework type and version
- Tailwind CSS version and config
- Path aliases
- Installed component list
- Icon library info
Before Skills, the AI guesses. After Skills, it reads your actual setup and generates code that matches — correct FieldGroup patterns, correct semantic color variables, correct import paths.
### 2\. MCP Server: Live Documentation Access
```
claude mcp add shadcn -- npx shadcn@latest mcp
```
Enter fullscreen modeExit fullscreen mode
MCP (Model Context Protocol) connects Claude Code directly to the shadcn/ui registry. During a conversation, the AI can:
- Look up component docs, examples, and props in real time
- Auto-generate install commands
- Search community registries
No more browser tabs. Ask "add sorting to my Data Table" and Claude Code pulls the latest docs, then writes the correct implementation.
### 3\. Preset: One-Line Design System
```
npx shadcn@latest init --preset a1Dg5eFl
```
Enter fullscreen modeExit fullscreen mode
A Preset packs colors, themes, icons, fonts, and border-radius into a single code. Build your design system visually at [shadcn/create](https://ui.shadcn.com/create), share the preset code with your team, and everyone — including Claude Code — uses the same design tokens.
Include the preset code in your AI prompts, and every generated component stays consistent with your design system.
## Before vs After
| | Before | After |
| --- | --- | --- |
| Context | Guesses from training data | Reads live project config |
| API accuracy | Frequent errors | Correct patterns from latest docs |
| Theme consistency | Different per page | Unified via CSS variables & preset |
| Doc lookup | Manual, ~15 min | MCP auto-search |
| Component scope | Basic UI only | Includes community registries |
## CLI v4 Commands You'll Actually Use
```
# Initialize with preset
npx shadcn@latest init --preset [CODE]
# Add components
npx shadcn@latest add button card dialog
# Install everything
npx shadcn@latest add --all
# Search registries
npx shadcn@latest search @shadcn
# Preview components
npx shadcn@latest view button card
# Look up docs
npx shadcn@latest docs data-table
# Dry run (preview changes)
npx shadcn@latest add --dry-run
# Show diff before applying
npx shadcn@latest add --diff
```
Enter fullscreen modeExit fullscreen mode
## FAQ
**Is shadcn/ui free?**
Yes. Fully open source, MIT license. Commercial use is fine.
**Does it work outside Next.js?**
CLI v4 supports Next.js, Remix, Vite, Astro, and more. It auto-detects your framework from `components.json`.
**Do I need all 3 settings?**
Skills alone gives you project context. But adding MCP Server (live docs) and Preset (design tokens) together gives the best results. The 3 tools complement each other.
**Can I add this to an existing project?**
Yes. If `components.json` exists, Skills picks it up automatically. For new projects, start with `npx shadcn@latest init`.
* * *
## Wrapping Up
3 commands, 5 minutes. Your AI stops guessing and starts reading your actual project. If you haven't set this up yet, start with `npx skills add shadcn/ui` — you'll see the difference on the first prompt.
## References
- [shadcn/ui CLI docs](https://ui.shadcn.com/docs/cli) — Full CLI v4 reference
- [shadcn/ui Skills docs](https://ui.shadcn.com/docs/skills) — AI agent Skills setup
- [shadcn/ui MCP Server docs](https://ui.shadcn.com/docs/registry/mcp) — MCP connection guide
- [shadcn/ui Changelog: CLI v4](https://ui.shadcn.com/docs/changelog/2026-03-cli-v4) — v4 release notes
[![profile](https://media2.dev.to/dynamic/image/width=64,height=64,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Forganization%2Fprofile_image%2F6839%2F3d85988f-d18e-4522-b261-f86613cd9b50.png)\\
Sonar](https://dev.to/sonar) Promoted
Dropdown menu
- [What's a billboard?](https://dev.to/billboards)
- [Manage preferences](https://dev.to/settings/customization#sponsors)
* * *
- [Report billboard](https://dev.to/report-abuse?billboard=259979)
[![State of Code Developer Survey report](https://media2.dev.to/dynamic/image/width=775%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fucarecdn.com%2F2f2ce9b0-68e0-48a1-bf3e-46c08831a9be%2F)](https://www.sonarsource.com/sem/the-state-of-code/developer-survey-report/?utm_medium=paid&utm_source=dev&utm_campaign=ss-state-of-code-developer-survey26&utm_content=report-devsurvey-banner-x-2&utm_term=ww-all-x&s_category=Paid&s_source=Paid+Social&s_origin=dev&bb=259979)
## [State of Code Developer Survey report](https://www.sonarsource.com/sem/the-state-of-code/developer-survey-report/?utm_medium=paid&utm_source=dev&utm_campaign=ss-state-of-code-developer-survey26&utm_content=report-devsurvey-banner-x-2&utm_term=ww-all-x&s_category=Paid&s_source=Paid+Social&s_origin=dev&bb=259979)
Did you know 96% of developers don't fully trust that AI-generated code is functionally correct, yet only 48% always check it before committing? Check out Sonar's new report on the real-world impact of AI on development teams.
[Read the results](https://www.sonarsource.com/sem/the-state-of-code/developer-survey-report/?utm_medium=paid&utm_source=dev&utm_campaign=ss-state-of-code-developer-survey26&utm_content=report-devsurvey-banner-x-2&utm_term=ww-all-x&s_category=Paid&s_source=Paid+Social&s_origin=dev&bb=259979)
Read More
![pic](https://media2.dev.to/dynamic/image/width=256,height=,fit=scale-down,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8j7kvp660rqzt99zui8e.png)
[Create template](https://dev.to/settings/response-templates)
Templates let you quickly answer FAQs or store snippets for re-use.
SubmitPreview [Dismiss](https://dev.to/404.html)
Some comments may only be visible to logged-in visitors. [Sign in](https://dev.to/enter) to view all comments.
Are you sure you want to hide this comment? It will become hidden in your post, but will still be visible via the comment's [permalink](https://dev.to/_46ea277e677b888e0cd13/shadcnui-claude-code-3-settings-that-fix-ai-generated-ui-quality-2dea#).
Hide child comments as well
Confirm
For further actions, you may consider blocking this person and/or [reporting abuse](https://dev.to/report-abuse)
[![profile](https://media2.dev.to/dynamic/image/width=64,height=64,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Forganization%2Fprofile_image%2F1%2Fd908a186-5651-4a5a-9f76-15200bc6801f.jpg)\\
The DEV Team](https://dev.to/devteam) Promoted
Dropdown menu
- [What's a billboard?](https://dev.to/billboards)
- [Manage preferences](https://dev.to/settings/customization#sponsors)
* * *
- [Report billboard](https://dev.to/report-abuse?billboard=262076)
[![Google article image](https://media2.dev.to/dynamic/image/width=775%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5s5sepylgzwml9we0agc.png)](https://dev.to/googleai/gemini-31-flash-lite-developer-guide-and-use-cases-1hh?bb=262076)
## [Gemini 3.1 Flash-Lite: Developer guide and use cases](https://dev.to/googleai/gemini-31-flash-lite-developer-guide-and-use-cases-1hh?bb=262076)
Gemini 3.1 Flash-Lite is the high-volume, affordable powerhouse of the Gemini family. Its purpose-built for large-scale tasks where speed and cost-efficiency are the main priorities, making it the ideal engine for background processing. Whether you're handling a constant stream of user interactions or need to process massive datasets with tasks like translation, transcription, or extraction, Flash-Lite provides the optimal balance of speed and capability.
This guide walks through seven practical use cases for Flash-Lite using the google-genai Python SDK.
[Read More](https://dev.to/googleai/gemini-31-flash-lite-developer-guide-and-use-cases-1hh?bb=262076)
👋 Kindness is contagious
Dropdown menu
- [What's a billboard?](https://dev.to/billboards)
- [Manage preferences](https://dev.to/settings/customization#sponsors)
* * *
- [Report billboard](https://dev.to/report-abuse?billboard=236872)
x
Discover fresh viewpoints in this insightful post, supported by our vibrant DEV Community. **Every developers experience matters**—add your thoughts and help us grow together.
A simple “thank you” can uplift the author and spark new discussions—leave yours below!
On DEV, **knowledge-sharing connects us and drives innovation**. Found this useful? A quick note of appreciation makes a real impact.
## [Okay](https://dev.to/enter?state=new-user&bb=236872)
![DEV Community](https://media2.dev.to/dynamic/image/width=190,height=,fit=scale-down,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8j7kvp660rqzt99zui8e.png)
We're a place where coders share, stay up-to-date and grow their careers.
[Log in](https://dev.to/enter?signup_subforem=1) [Create account](https://dev.to/enter?signup_subforem=1&state=new-user)
![](https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg)![](https://assets.dev.to/assets/multi-unicorn-b44d6f8c23cdd00964192bedc38af3e82463978aa611b4365bd33a0f1f4f3e97.svg)![](https://assets.dev.to/assets/exploding-head-daceb38d627e6ae9b730f36a1e390fca556a4289d5a41abb2c35068ad3e2c4b5.svg)![](https://assets.dev.to/assets/raised-hands-74b2099fd66a39f2d7eed9305ee0f4553df0eb7b4f11b01b6b1b499973048fe5.svg)![](https://assets.dev.to/assets/fire-f60e7a582391810302117f987b22a8ef04a2fe0df7e3258a5f49332df1cec71e.svg)

View File

@ -1,149 +0,0 @@
[Stop Rebuilding UI From ScratchStop Rebuilding UI](https://www.shadcn.io/pricing)
Shadcn.io is not affiliated with official [shadcn/ui](https://ui.shadcn.com/)
[Previous: Shadcn UI React Components](https://www.shadcn.io/ui) Ask AI
[Discord](https://discord.gg/Z9NVtNE7bj "Join Discord") [GitHub](https://github.com/shadcnio/react-shadcn-components "View on GitHub") [Next: Shadcn Installation Guide](https://www.shadcn.io/ui/installation-guide)
# Why AI Coding Tools Love Shadcn UI
Why shadcn UI works perfectly with AI coding tools like Cursor, Copilot, and v0. Copy-paste React components with TypeScript for Next.js applications.
Table of Contents
# [Why AI Coding Tools Love React Components You Actually Own](https://www.shadcn.io/ui/why-ai-coding-tools-love-shadcn-ui\#why-ai-coding-tools-love-react-components-you-actually-own)
Building React applications with AI coding tools in 2024 means your AI assistant needs to understand your components. Ask it to "make the button green and add a loading spinner" with most UI libraries, and your AI basically throws up its hands. "Well, you could try overriding the theme... or maybe use a CSS class... actually, let me check the docs..." Sound familiar?
But with shadcn/ui React components? Your AI just opens up `components/ui/button.tsx`, sees exactly how everything works with TypeScript, changes `bg-blue-500` to `bg-green-500`, tosses in a spinner, updates the props interface. Boom. Done.
Here's what nobody talks about: the best React component library for AI coding tools isn't the one with the most features—it's the one your AI can actually read and modify with TypeScript.
## [The black box problem we've all lived with](https://www.shadcn.io/ui/why-ai-coding-tools-love-shadcn-ui\#the-black-box-problem-weve-all-lived-with)
Look, we've all been there. You're using Material UI or Chakra in your Next.js application, and you want to make one tiny change to a React component. You end up in this rabbit hole of theme providers, style overrides, and documentation diving. It's frustrating enough when you're doing it yourself.
Now imagine your AI coding tool trying to help. It's basically playing blind chess. All the actual component logic is compiled and hidden away in `node_modules`. Your AI can see the public API, sure, but it has no clue how anything actually works under the hood with TypeScript.
Want to modify a button's behavior? Your AI starts guessing: "Maybe try the theme object? Or this CSS-in-JS prop? Actually, let me search the docs..." Meanwhile, you're sitting there thinking, "Just change the damn button."
## [shadcn/ui flipped the script](https://www.shadcn.io/ui/why-ai-coding-tools-love-shadcn-ui\#shadcnui-flipped-the-script)
Here's what shadcn/ui did differently. Instead of giving you another black box library, it just... gives you the code. Seriously, that's it.
Run `npx shadcn@latest add button` and boom—you've got a `button.tsx` file with TypeScript sitting right there in your Next.js codebase. Your AI coding tool can read it like any other React component in your project. No mysteries, no abstractions, no "the magic happens elsewhere."
And here's the kicker: it's all built with Tailwind CSS. Classes like `bg-primary hover:bg-primary/90` tell the whole story. Your AI doesn't need to decode some complex theme system—it can literally see that hovering makes the background 90% opacity.
Want a loading state? Your AI looks at the component, sees how variants work, and just... adds one. No theme provider wrestling, no API limitations. Just code doing what code does.
```
// Before: Simple button
<Button variant="default">Click me</Button>
// After: AI adds loading state by reading the component
<Button variant="default" loading={isLoading}>
{isLoading ? <Loader2 className="mr-2 h-4 w-4 animate-spin" /> : null}
Click me
</Button>
```
## [Why AI gets shadcn/ui so well](https://www.shadcn.io/ui/why-ai-coding-tools-love-shadcn-ui\#why-ai-gets-shadcnui-so-well)
Think about it—AI is really good at pattern recognition and code modification. But it's only as good as what it can see.
With Tailwind CSS, everything's explicit in your React components. `text-blue-500` always means the same thing. Your AI coding tool reads `hover:bg-blue-600 focus:ring-2 focus:ring-blue-500` and immediately knows what's going on. No theme system to decode, no CSS-in-JS abstractions to parse in your TypeScript files.
After your AI sees a couple shadcn/ui React components, it starts picking up on the patterns. How variants work with TypeScript, how you structure Next.js layouts, even your accessibility patterns. It's like having a junior dev who learns your coding style really, really fast.
The difference? With traditional libraries, all these patterns are hidden in theme configs and style overrides. With shadcn/ui, they're right there in the component files where your AI can see them.
```
// What your AI sees in button.tsx
const buttonVariants = cva(
"inline-flex items-center justify-center rounded-md text-sm font-medium",
{
variants: {
variant: {
default: "bg-primary text-primary-foreground hover:bg-primary/90",
destructive:
"bg-destructive text-destructive-foreground hover:bg-destructive/90",
outline: "border border-input bg-background hover:bg-accent",
// AI can easily add new variants here
loading: "bg-primary/50 text-primary-foreground cursor-not-allowed",
},
},
}
);
```
## [What this actually means for your workflow](https://www.shadcn.io/ui/why-ai-coding-tools-love-shadcn-ui\#what-this-actually-means-for-your-workflow)
Here's the crazy part: shadcn/ui wasn't designed for AI at all. It was designed to solve the developer control problem. But in doing that, it accidentally solved the AI problem too.
Your AI coding tool doesn't need docs anymore—it just reads your React components with TypeScript. When it suggests changes, they actually work because it understands the real implementation in your Next.js application, not some abstract API description. And the more you customize your components, the better your AI gets at understanding your specific style.
## [The bigger pattern emerging](https://www.shadcn.io/ui/why-ai-coding-tools-love-shadcn-ui\#the-bigger-pattern-emerging)
You know what's interesting? This transparency thing isn't just winning in React and Next.js development. Look around—AI coding tools work better with anything they can actually read and understand.
Terraform configs vs complex deployment abstractions. Raw SQL schemas vs ORM magic. OpenAPI specs vs undocumented REST APIs. TypeScript interfaces vs any types. Every time, the transparent, declarative approach wins when AI gets involved.
shadcn/ui just happened to stumble onto this principle first in the React component library space. But the pattern is clear: if you want to build something that works well with AI coding tools, make it readable with TypeScript, make it explicit, and put it where AI can see it in your Next.js codebase.
## [What comes next](https://www.shadcn.io/ui/why-ai-coding-tools-love-shadcn-ui\#what-comes-next)
We're still in the early days, but the trajectory is clear. As AI coding tools get better at understanding React component patterns with TypeScript, they amplify shadcn/ui's transparency advantage. More Next.js projects adopt these patterns, creating better training data, which makes AI tools even more effective. It's a virtuous cycle.
We're not just building better UIs anymore. We're building UIs that think.
## [The simple truth](https://www.shadcn.io/ui/why-ai-coding-tools-love-shadcn-ui\#the-simple-truth)
Here's the beautiful part: you don't need to change anything. Use shadcn/ui React components with TypeScript exactly as you normally would in your Next.js application. The AI compatibility comes from the transparency, not special APIs or patterns.
shadcn/ui accidentally solved the AI coding tool compatibility problem by solving the developer control problem. When you own your React component code with TypeScript, both you and your AI can understand it. The result? A development experience that feels like having a pair programmer who actually understands your Next.js codebase.
## [AI-Friendly Components to Try](https://www.shadcn.io/ui/why-ai-coding-tools-love-shadcn-ui\#ai-friendly-components-to-try)
These React components work exceptionally well with AI coding tools because of their transparent, readable structure:
[**Button** \\
Variants and states that AI can easily read and modify](https://www.shadcn.io/ui/button) [**Input** \\
Form components with clear TypeScript interfaces](https://www.shadcn.io/ui/input) [**Dialog** \\
Complex components AI can understand and customize](https://www.shadcn.io/ui/dialog) [**Form** \\
Validation patterns AI can replicate and extend](https://www.shadcn.io/ui/form) [**Data Table** \\
Advanced components with clear sorting and filtering logic](https://www.shadcn.io/ui/data-table) [**Command** \\
Search interfaces AI can enhance with new features](https://www.shadcn.io/ui/command)
## [Ready to try it yourself?](https://www.shadcn.io/ui/why-ai-coding-tools-love-shadcn-ui\#ready-to-try-it-yourself)
Pick your framework and get started with shadcn/ui. The [installation guide](https://www.shadcn.io/ui/installation-guide) walks you through setup for Next.js, Vite, Remix, and more. Takes about 5 minutes to get your first component working.
Once you're set up, here's where to go next:
1. Browse [official components](https://www.shadcn.io/ui) for forms, tables, and UI elements
2. Add [charts](https://www.shadcn.io/charts) for data visualization
3. Explore [community components](https://www.shadcn.io/components) for extended functionality
4. Add useful [hooks](https://www.shadcn.io/hooks) to enhance your components
5. Use pre-built [blocks](https://www.shadcn.io/blocks) to quickly build common layouts
Then ask your AI assistant to modify something. Watch it actually understand your components instead of guessing from docs. You'll never want to go back to black box libraries again.
## [Questions you're probably thinking](https://www.shadcn.io/ui/why-ai-coding-tools-love-shadcn-ui\#questions-youre-probably-thinking)
### Do I need to learn new patterns for AI-assisted development?
### Will this work with future AI coding tools?
### Are other libraries adopting this approach?
Was this page helpful?
[Sign in](https://www.shadcn.io/sign-in) to leave feedback.
[Shadcn UI React Components\\
\\
Copy-paste React components built with Radix UI and Tailwind CSS. Open source component library for Next.js with TypeScript support and full code ownership.](https://www.shadcn.io/ui) [Shadcn Installation Guide\\
\\
Setup guides for shadcn/ui with Next.js, Vite, Remix, Laravel, Astro, and more React frameworks. Get started with TypeScript components quickly.](https://www.shadcn.io/ui/installation-guide)

View File

@ -1,151 +0,0 @@
Context
# Context
Context
Context
Context
Size
263 B
[NPM\\
\\
v0.3.1](https://www.npmjs.com/package//@solid-primitives/context)
Stage
2
## [\#](https://primitives.solidjs.community/package/context/\#installation) Installation
Copy
npm install @solid-primitives/context
Copy
yarn add @solid-primitives/context
Copy
pnpm add @solid-primitives/context
## [\#](https://primitives.solidjs.community/package/context/\#readme) Readme
Primitives simplifying the creation and use of SolidJS Context API.
- [`createContextProvider`](https://primitives.solidjs.community/package/context/#createcontextprovider) \- Create the Context Provider component and useContext function with types inferred from the factory function.
- [`MultiProvider`](https://primitives.solidjs.community/package/context/#multiprovider) \- A component that allows you to provide multiple contexts at once.
## [\#](https://primitives.solidjs.community/package/context/\#createcontextprovider)`createContextProvider`
Create the Context Provider component and useContext function with types inferred from the factory function.
### [\#](https://primitives.solidjs.community/package/context/\#how-to-use-it) How to use it
Given a factory function, `createContextProvider` creates a SolidJS Context and returns both a Provider component for setting the context, and a useContext helper for getting the context. The factory function gets called when the provider component gets executed; all `props` of the provider component get passed into the factory function, and what it returns will be available in the contexts for all the underlying components. The types of the provider props and context are inferred from the factory function.
```tsx
import { createContextProvider } from "@solid-primitives/context";
const [CounterProvider, useCounter] = createContextProvider((props: { initial: number }) => {
const [count, setCount] = createSignal(props.initial);
const increment = () => setCount(count() + 1);
return { count, increment };
});
// Provide the context
<CounterProvider initial={1}>
<App />
</CounterProvider>;
// Use the context in a child component
const ctx = useCounter();
ctx; // T: { count: () => number; increment: () => void; } | undefined
```
### [\#](https://primitives.solidjs.community/package/context/\#providing-context-fallback) Providing context fallback
The `createContextProvider` primitive takes a second, optional argument for providing context defaults for when the context wouldn't be provided higher in the component tree.
Providing a fallback also removes `undefined` from `T | undefined` return type of the `useContext` function.
```ts
const [CounterProvider, useCounter] = createContextProvider(
() => {
const [count, setCount] = createSignal(0);
const increment = () => setCount(count() + 1);
return { count, increment };
},
{
count: () => 0,
increment: () => {},
},
);
// then when using the context:
const { count } = useCounter();
```
Definite context types without defaults:
```ts
const useDefiniteCounter = () => useCounter()!;
```
### [\#](https://primitives.solidjs.community/package/context/\#demo) Demo
[https://codesandbox.io/s/solid-primitives-context-demo-oqyie2?file=/index.tsx](https://codesandbox.io/s/solid-primitives-context-demo-oqyie2?file=/index.tsx)
## [\#](https://primitives.solidjs.community/package/context/\#multiprovider)`MultiProvider`
A component that allows you to provide multiple contexts at once.
It will work exactly like nesting multiple providers as separate components, but it will save you from the nesting.
### [\#](https://primitives.solidjs.community/package/context/\#how-to-use-it-1) How to use it
`MultiProvider` takes only a single `values` with a key-value pair of the context and the value to provide.
> **Note**
> Values list is evaluated in order, so the context values will be provided in the same way as if you were nesting the providers.
```tsx
import { MultiProvider } from "@solid-primitives/context";
// before
<FooContext.Provider value={"foo"}>
<BarContext.Provider value={"bar"}>
<BazContext.Provider value={"baz"}>
<MyCustomProviderComponent value={"hello-world"}>
<BoundContextProvider>
<App />
</BoundContextProvider>
</MyCustomProviderComponent>
</BazContext.Provider>
</BarContext.Provider>
</FooContext.Provider>;
// after
<MultiProvider
values={[\
[FooContext, "foo"],\
[BarContext, "bar"],\
[BazContext, "baz"],\
// you can also provide a component, the value will be passed to a `value` prop\
[MyCustomProviderComponent, "hello-world"],\
// if you have a provider that doesn't accept a `value` prop, you can just pass a function\
BoundContextProvider,\
]}
>
<App />
</MultiProvider>;
```
> **Warning**
> Components and values passed to `MultiProvider` will be evaluated only once, so make sure that the structure is static. If is isn't, please use nested provider components instead.
## [\#](https://primitives.solidjs.community/package/context/\#changelog) Changelog
See [CHANGELOG.md](https://github.com/solidjs-community/solid-primitives/blob/main/packages/context/CHANGELOG.md)

View File

@ -1,281 +0,0 @@
As more developers are adopting [SolidJS](https://www.solidjs.com/?ref=yon.fun) for its fine-grained reactivity and impressive performance, finding the right UI library that is easy to use, flexible, and efficient has become even more important.
The right UI components can make a significant difference in how quickly and smoothly your project gets completed, ensuring both a great developer experience and user satisfaction.
According to [recent statistics](https://www.statista.com/statistics/1124699/worldwide-developer-survey-most-used-frameworks-web/?ref=yon.fun), SolidJS is used by 1.2% of developers worldwide (as of 2024), indicating that more developers are seeking powerful tools to improve their productivity.
![Solid.js Usage 1.2% as of 2024](https://yon.fun/content/images/2024/11/image-9.png)SolidJS Usage Worldwide as of 2024
In this article, we'll talk about some of the [best UI libraries for SolidJS](https://yon.fun/solidjs-ui-libs/), ranked by how complex they are, their features, and how easy they are to use.
Let's explore what each library offers and how it can fit your development needs!
[Top 15 Best Lightweight CSS Frameworks (JS-Free)\\
\\
Find the best lightweight CSS frameworks for responsive, customizable, and JS-free solutions. Discover our top 15 picks, designed to simplify your design process and improve website performance.\\
\\
![](https://yon.fun/content/images/size/w256h256/2024/03/favicon-60.png)The Art of Dev.Ion Prodan\\
\\
![](https://yon.fun/content/images/2023/04/Top-15-Best-Lightweight-CSS-Frameworks.png)](https://yon.fun/top-10-css-frameworks/)
## 1\. Kobalte
![](https://yon.fun/content/images/2024/11/image-17.png)Kobalte Homepage
[Kobalte](https://kobalte.dev/docs/core/overview/introduction?ref=yon.fun) is a UI toolkit designed to build accessible web apps and design systems with SolidJS. It offers a collection of low-level components and primitives that provide the building blocks for creating a design system from scratch.
**Accessibility is at the forefront of Kobalte**, making it an ideal option for developers prioritizing inclusive design.
### Why Choose Kobalte?
_Kobalte is an excellent choice if accessibility is a priority for your project_. It offers fine-grained component control and a high level of flexibility, ideal for building truly custom user interfaces.
The unstyled approach allows developers to bring in their preferred styling solutions, whether it's vanilla CSS, Tailwind, or CSS-in-JS.
### Pros:
- Focuses on accessibility following [WAI-ARIA Authoring](https://www.w3.org/WAI/ARIA/apg/?ref=yon.fun) Practices.
- Composable components with granular access to component parts.
- Fully unstyled, allowing for complete customization.
### Cons:
- Requires more effort for styling since components are unstyled by default.
## 2\. SUID
![](https://yon.fun/content/images/2024/11/image-10.png)SUID homepage
[SUID](https://suid.io/?ref=yon.fun) is a popular UI library built on [Material-UI (MUI)](https://mui.com/?ref=yon.fun) but made to work with SolidJS.
**It has over 50 components with full TypeScript support and works well with your SolidJS projects.**
SUID keeps the same style as MUI, which makes it great for developers who have used MUI before but want to work with SolidJS. It also comes with material design themes and is known for its flexibility and modern look.
### Why Choose SUID?
If you're used to React and MUI, this library will feel familiar. It provides extensive customization options, making it versatile for many use cases.
However, if you need a very lightweight solution, SUID might not be the best choice since it can be a bit heavy due to Material-UI features.
### Pros:
- Familiar API for developers transitioning from React and MUI.
- Over 50 components with TypeScript support.
- Flexible and modern material design themes.
### Cons:
- Inherits some of the complexity and overhead from Material-UI.
- Not the most lightweight solution available.
## 3\. Solid Bootstrap
![](https://yon.fun/content/images/2024/11/image-11.png)
[Solid Bootstrap](https://solid-libs.github.io/solid-bootstrap/?ref=yon.fun) is a version of the popular Bootstrap library for SolidJS. It's great for developers who want to build quickly and without much fuss.
It has all the classic Bootstrap components like buttons, cards, and modals, and makes them work well with SolidJS.
### Why Choose Solid Bootstrap?
This library is ideal for those who prefer the classic Bootstrap look and want to build fast without worrying about complex customizations.
For example, Solid Bootstrap works well for building administrative dashboards where you need standard UI elements like tables, forms, and navigation bars quickly and easily.
### Pros:
- Easy to integrate with existing Bootstrap styles.
- Ideal for developers already familiar with Bootstrap.
- Great for quickly building admin dashboards.
### Cons:
- Limited flexibility in customizing beyond Bootstrap's standard components.
- May not be ideal for highly unique or modern UI designs.
## 4\. Flowbite Solid
![](https://yon.fun/content/images/2024/11/image-12.png)
[Flowbite Solid](https://flowbite.com/docs/getting-started/solid-js/?ref=yon.fun) is a set of UI components built with Tailwind CSS and designed for SolidJS. It helps you create clean and responsive designs easily.
Flowbite components are very customizable and use Tailwind's utility classes, which means you can adjust things like spacing, colors, and fonts easily to make sure your design matches your brand.
### Why Choose Flowbite Solid?
If you are a fan of Tailwind CSS and prefer using utility classes to maintain flexibility in styling, Flowbite Solid is a solid option.
Its responsive and accessible components make it perfect for projects that need a consistent, visually appealing design.
### Pros:
- Fully compatible with Tailwind CSS, making it easy to customize.
- Responsive and accessible components.
- Great for building visually appealing, consistent designs.
### Cons:
- Requires familiarity with Tailwind CSS for effective use.
- Moderate complexity may not suit quick or simple projects.
[7 Best Free No-JS Tailwind CSS Component Libraries\\
\\
Discover the top 7 lightweight Tailwind CSS component libraries that require no JavaScript. These libraries offer customizable, responsive UI components to streamline your development process and create professional, consistent designs quickly and efficiently.\\
\\
![](https://yon.fun/content/images/size/w256h256/2024/03/favicon-60.png)The Art of Dev.Ion Prodan\\
\\
![](https://yon.fun/content/images/2024/07/Top-Lightweight-Tailwind-Component-Libraries.png)](https://yon.fun/top-tailwind-component-libs/)
## 5\. Ark UI
![](https://yon.fun/content/images/2024/11/image-13.png)Ark UI Homepage
[Ark UI](https://ark-ui.com/?ref=yon.fun) is a headless UI library, which means it lets you create fully customizable components. It works with many JavaScript frameworks like SolidJS, React, and Vue.
Ark UI is focused on giving developers lots of flexibility without forcing any specific styles, so you can create unique and accessible components.
### Why Choose Ark UI?
If you want full control over how your components look and aim to create something truly unique, Ark UI is a powerful option.
However, because it is a headless library, it has a steeper learning curve and requires more setup and styling compared to other options.
### Pros:
- Full control over styling with headless components.
- Supports multiple frameworks, including SolidJS, React, and Vue.
- Highly customizable and accessible.
### Cons:
- Steeper learning curve compared to styled component libraries.
- Requires more effort to style and set up compared to simpler libraries.
## 6\. SolidUI
![](https://yon.fun/content/images/2024/11/image-14.png)SolidUI Homepage
[SolidUI](https://www.solid-ui.com/?ref=yon.fun) gives you a set of nice-looking components that you can easily use in your SolidJS projects. It is easy to customize and is perfect for making quick prototypes or simple web interfaces without much effort.
SolidUI is also an unofficial port of [shadcn/ui](https://ui.shadcn.com/?ref=yon.fun) to SolidJS and is not affiliated with @shadcn.
When working with SolidJS and libraries like Solid-UI, you'll often deal with reactive components.
```ts
const [count, setCount] = createSignal(0);
```
If you need to organize and snapshot these code pieces efficiently, I recommend [SnipsCo](https://snipsco.com/?ref=yon.fun) \- it's great for developers managing multiple UI snippets.
### Why Choose SolidUI?
If you want a simple and fast solution with ready-made components, SolidUI is an excellent option.
Compared to Solid Bootstrap, SolidUI is geared towards providing out-of-the-box components, helping you start building immediately without spending time on extra customization.
As it is an unofficial port of shadcn/ui, it offers a similar experience for developers already familiar with shadcn.
### Pros:
- Easy drag-and-drop integration.
- Customizable components that work well without much effort.
- Great for quick prototyping.
### Cons:
- Limited customization compared to headless or more complex libraries.
- Not ideal for larger, complex projects that need advanced functionality.
## 7\. shadcn-solid
![](https://yon.fun/content/images/2024/11/image-15.png)
[shadcn-solid](https://shadcn-solid.com/?ref=yon.fun) is also an unofficial version of the popular [shadcn/ui library](https://ui.shadcn.com/?ref=yon.fun) made for SolidJS.
It has lots of customizable and accessible components that are easy to add to your SolidJS apps. The goal of this library is to give developers a smooth experience by providing consistent design and tools that are easy to use.
### Why Choose shadcn-solid?
If you like the design and usability of shadcn/ui and wish to adapt it for SolidJS, this library is ideal.
It works well for projects that need a consistent design across different platforms, providing accessible and easily customizable components.
### Pros:
- Based on the popular shadcn/ui, making it familiar to many developers.
- Components are accessible and easy to customize.
- Suitable for projects needing a consistent design across different platforms.
### Cons:
- Less feature-rich compared to some other medium or high-complexity libraries.
- Customization is easier, but can still be limited by the library's design choices.
## 8\. Corvu
![](https://yon.fun/content/images/2024/11/image-16.png)Corvu Homepage
[Corvu](https://corvu.dev/?ref=yon.fun) is a collection of basic UI building blocks for SolidJS. Unlike other libraries, **Corvu does not come with pre-styled components**, which means you have full control over the look and feel of your app.
It's perfect if you want to create something truly custom and don't mind doing all the styling work yourself.
### Why Choose Corvu?
If you want to create your UI from scratch and have full control over every detail, Corvu is the perfect tool.
It's great for developers who are comfortable doing their styling and want to create a highly unique, custom UI.
### Pros:
- Provides maximum control by offering basic UI building blocks.
- Perfect for creating highly unique and custom UIs.
- Focused on accessibility and developer flexibility.
### Cons:
- Requires developers to handle all aspects of styling.
- Not suitable for those who need ready-to-use components.
## Comparison Table of SolidJS UI Libraries
| Library Name | Complexity | Key Features | Best Use Case |
| --- | --- | --- | --- |
| Kobalte | Medium | Accessible, composable, unstyled | Building accessible and fully custom UIs |
| SUID | Medium | TypeScript support, Material-UI API | Transitioning from React to SolidJS |
| Solid Bootstrap | Low | Bootstrap components, easy integration | Quick dashboards and admin panels |
| Flowbite Solid | Medium | Tailwind CSS integration, responsive components | Projects using Tailwind CSS for styling |
| Ark UI | High | Headless components, full customization | Unique, highly customized UI projects |
| SolidUI | Low | Pre-designed components, easy setup | Rapid prototyping and simple web interfaces |
| shadcn-solid | Medium | Based on shadcn/ui, customizable components | Consistent design across platforms |
| Corvu | High | UI primitives, no default styles | Highly custom, unique styling from scratch |
_Table 1: Discover the best UI libraries for SolidJS, comparing complexity, key features, and the best use cases to help you choose the right tool for your project._
## Conclusion
Choosing the right UI library for your SolidJS project depends on what you need: _speed, customization, or something in between._
[Kobalte](https://yon.fun/solidjs-ui-libs/#1-kobalte) is ideal if accessibility and composability are crucial, [SUID](https://yon.fun/solidjs-ui-libs/#2-suid) is great if you're used to React, [Solid Bootstrap](https://yon.fun/solidjs-ui-libs/#3-solid-bootstrap) is perfect if you like Bootstrap, and [Flowbite Solid](https://yon.fun/solidjs-ui-libs/#4-flowbite-solid) is ideal if you love Tailwind CSS.
For maximum control, [**Ark UI**](https://yon.fun/solidjs-ui-libs/#5-ark-ui) **and** [**Corvu**](https://yon.fun/solidjs-ui-libs/#8-corvu) **are good choices because they let you design however you want.**
If you like shadcn/ui, [shadcn-solid](https://yon.fun/solidjs-ui-libs/#7-shadcn-solid), and [SolidUI](https://yon.fun/solidjs-ui-libs/#6-solidui) are a great version of SolidJS.
We hope this guide helps you pick the best UI library for your next SolidJS project!
## FAQs:
### Which SolidJS UI library is best for beginners?
[SolidUI](https://yon.fun/solidjs-ui-libs/#6-solidui) and [Solid Bootstrap](https://yon.fun/solidjs-ui-libs/#3-solid-bootstrap) are ideal for beginners because they are easy to use and don't require a lot of custom styling.
### Can I use Bootstrap with SolidJS?
Yes, you can use Solid Bootstrap, which is a version of the popular Bootstrap library adapted for SolidJS.
### What are the benefits of using Ark UI for SolidJS?
Ark UI allows for complete customization, making it suitable for projects where you need a unique look and want full control over component styling.
Feel free to check out our further reading on SolidJS best practices to take your development to the next level.

Some files were not shown because too many files have changed in this diff Show More