diff --git a/src/uix/eidos/components/select/index.ts b/src/uix/eidos/components/select/index.ts
index a2d2b73ae..b450942d9 100644
--- a/src/uix/eidos/components/select/index.ts
+++ b/src/uix/eidos/components/select/index.ts
@@ -5,6 +5,7 @@ import Indicator from './select-indicator.svelte'
import Content from './select-content.svelte'
import Viewport from './select-viewport.svelte'
import Item from './select-item.svelte'
+import ItemDescription from './select-item-description.svelte'
import ItemIndicator from './select-item-indicator.svelte'
import Group from './select-group.svelte'
import GroupHeading from './select-group-heading.svelte'
@@ -20,6 +21,7 @@ type SelectNamespace = typeof SelectComponent & {
Content: typeof Content
Viewport: typeof Viewport
Item: typeof Item
+ ItemDescription: typeof ItemDescription
ItemIndicator: typeof ItemIndicator
Group: typeof Group
GroupHeading: typeof GroupHeading
@@ -35,6 +37,7 @@ Select.Portal = Portal
Select.Content = Content
Select.Viewport = Viewport
Select.Item = Item
+Select.ItemDescription = ItemDescription
Select.ItemIndicator = ItemIndicator
Select.Group = Group
Select.GroupHeading = GroupHeading
@@ -53,6 +56,7 @@ export type {
SelectContentProps as ContentProps,
SelectViewportProps as ViewportProps,
SelectItemProps as ItemProps,
+ SelectItemDescriptionProps as ItemDescriptionProps,
SelectItemIndicatorProps as ItemIndicatorProps,
SelectGroupProps as GroupProps,
SelectGroupHeadingProps as GroupHeadingProps,
diff --git a/src/uix/eidos/components/select/select-item-description.svelte b/src/uix/eidos/components/select/select-item-description.svelte
new file mode 100644
index 000000000..f0d1488d8
--- /dev/null
+++ b/src/uix/eidos/components/select/select-item-description.svelte
@@ -0,0 +1,10 @@
+
+
+
+ {@render children?.()}
+
diff --git a/src/uix/eidos/components/select/select.css b/src/uix/eidos/components/select/select.css
index 1f8ae8363..d8279ade6 100644
--- a/src/uix/eidos/components/select/select.css
+++ b/src/uix/eidos/components/select/select.css
@@ -166,9 +166,24 @@
cursor: not-allowed;
}
+/* Invalid state — mirror field.css conventions. Border uses the risk palette
+ token; on focus we still keep the risk border (not the accent) so users
+ keep seeing the warning while interacting. */
+[data-select-trigger][data-invalid] {
+ border-color: var(--color-risk-border);
+}
+
+[data-select-trigger][data-invalid]:focus-visible,
+[data-select-trigger][data-invalid][data-state='open'] {
+ border-color: var(--color-risk-border);
+ box-shadow: 0 0 0 var(--select-trigger-border-width) var(--color-risk-border);
+}
+
[data-select-value] {
+ display: block;
flex: 1 1 auto;
min-inline-size: 0;
+ max-inline-size: 100%;
overflow: hidden;
text-align: start;
text-overflow: ellipsis;
@@ -324,7 +339,10 @@
color var(--select-transition-duration) var(--select-transition-ease);
}
-[data-select-item][data-highlighted] {
+/* Keyboard-focus background only when the item is NOT also checked. The
+ checked-and-highlighted case is handled below with an inset ring so the
+ accent track stays visible. */
+[data-select-item][data-highlighted]:not([data-state='checked']) {
background: var(--select-item-bg-highlighted);
color: var(--select-item-color-highlighted);
}
@@ -334,11 +352,32 @@
color: var(--_select-accent-text);
}
+/* Highlighted + checked: keep accent track but layer an inset focus shadow
+ so the row reads as "this checked item is the keyboard focus". */
+[data-select-item][data-highlighted][data-state='checked'] {
+ box-shadow: inset 0 0 0 var(--select-item-focus-ring-width, 2px) var(--_select-accent-border);
+}
+
[data-select-item][data-disabled] {
color: var(--select-disabled-color);
cursor: not-allowed;
}
+/* Secondary descriptive text under the item label (React Aria parity). */
+[data-select-item-description] {
+ display: block;
+ margin-block-start: var(--select-item-description-mt, 0.125rem);
+ color: var(--select-item-description-color, var(--color-content-muted));
+ font-size: var(--select-item-description-font-size, 0.85em);
+ line-height: var(--select-line-height);
+ opacity: 0.8;
+}
+
+[data-select-item][data-state='checked'] [data-select-item-description] {
+ color: inherit;
+ opacity: 0.85;
+}
+
[data-select-item-indicator] {
display: inline-flex;
flex: 0 0 auto;
diff --git a/src/uix/eidos/components/select/types.ts b/src/uix/eidos/components/select/types.ts
index fcbab57b1..9d716f290 100644
--- a/src/uix/eidos/components/select/types.ts
+++ b/src/uix/eidos/components/select/types.ts
@@ -3,6 +3,7 @@ import type {
ContentProps,
GroupHeadingProps,
GroupProps,
+ ItemDescriptionProps,
ItemProps,
ProviderProps,
SeparatorProps,
@@ -48,6 +49,7 @@ export type SelectContentProps = ContentProps & {
export type SelectViewportProps = ViewportProps
export type SelectItemProps = ItemProps
+export type SelectItemDescriptionProps = ItemDescriptionProps
export type SelectGroupProps = GroupProps
export type SelectGroupHeadingProps = GroupHeadingProps
export type SelectSeparatorProps = SeparatorProps
diff --git a/src/uix/morfo/components/select.ts b/src/uix/morfo/components/select.ts
index a3c7343a0..deb36a3b6 100644
--- a/src/uix/morfo/components/select.ts
+++ b/src/uix/morfo/components/select.ts
@@ -8,7 +8,7 @@ export const selectMorfo = {
texts: {
label: '#?components.select.label|Select'
},
- apg: 'https://www.w3.org/WAI/ARIA/apg/patterns/combobox/',
+ apg: 'https://www.w3.org/WAI/ARIA/apg/patterns/listbox/',
events: [
{
@@ -52,9 +52,12 @@ export const selectMorfo = {
data: [
{ attr: 'data-state', values: ['open', 'closed'], value: v.stateRef('open') },
{ attr: 'data-disabled', value: v.propRef('disabled'), severity: 'optional' },
+ { attr: 'data-invalid', value: v.propRef('invalid'), severity: 'optional' },
{ attr: 'data-type', values: ['single', 'multiple'], value: v.propRef('type') }
],
- aria: []
+ aria: [
+ { attr: 'aria-invalid', value: v.propRef('invalid'), severity: 'optional' }
+ ]
},
{
name: 'Trigger',
@@ -68,6 +71,7 @@ export const selectMorfo = {
data: [
{ attr: 'data-state', values: ['open', 'closed'] },
{ attr: 'data-disabled', severity: 'optional' },
+ { attr: 'data-invalid', severity: 'optional' },
{ attr: 'data-placeholder', severity: 'optional' }
],
aria: [
@@ -169,6 +173,16 @@ export const selectMorfo = {
}
]
},
+ {
+ name: 'ItemDescription',
+ kebab: 'item-description',
+ archetype: 'description',
+ kind: 'public',
+ defaultElement: 'span',
+ optional: true,
+ data: [],
+ aria: []
+ },
{
name: 'Group',
kebab: 'group',
diff --git a/src/uix/soma/components/select/components/select-item-description.svelte b/src/uix/soma/components/select/components/select-item-description.svelte
new file mode 100644
index 000000000..6298021fa
--- /dev/null
+++ b/src/uix/soma/components/select/components/select-item-description.svelte
@@ -0,0 +1,35 @@
+
+
+{#if child}
+ {@render child({ props: mergedProps })}
+{:else}
+
+ {@render children?.()}
+
+{/if}
diff --git a/src/uix/soma/components/select/components/select.svelte b/src/uix/soma/components/select/components/select.svelte
index 7ce3da134..01ddd15a8 100644
--- a/src/uix/soma/components/select/components/select.svelte
+++ b/src/uix/soma/components/select/components/select.svelte
@@ -1,4 +1,5 @@