diff --git a/docs/_sidebar.md b/docs/_sidebar.md
index 86dd8f062..5f0fbe19c 100644
--- a/docs/_sidebar.md
+++ b/docs/_sidebar.md
@@ -21,6 +21,7 @@
- [Dropdown](/components/dropdown.md)
- [Form](/components/form.md)
- [Icon](/components/icon.md)
+ - [Icon Button](/components/icon-button.md)
- [Input](/components/input.md)
- [Menu](/components/menu.md)
- [Menu Divider](/components/menu-divider.md)
diff --git a/docs/components/icon-button.md b/docs/components/icon-button.md
new file mode 100644
index 000000000..5c8ae1e98
--- /dev/null
+++ b/docs/components/icon-button.md
@@ -0,0 +1,60 @@
+# Icon Button
+
+[component-header:sl-icon-button]
+
+Icons buttons are simple, icon-only buttons that can be used for actions and in toolbars.
+
+For a full list of icons that come bundled with Shoelace, refer to the [icon component](/components/icon).
+
+```html preview
+
+
+
+```
+
+## Examples
+
+### Sizes
+
+Icon buttons inherit their parent element's `font-size`.
+
+```html preview
+
+
+
+```
+
+### Colors
+
+You can customize icon button's color by styling its `base` part.
+
+```html preview
+
+
+
+```
+
+### Icon Button with Tooltip
+
+Wrap a tooltip around an icon button to provide contextual information to the user.
+
+```html preview
+
+
+
+```
+
+### Disabled
+```html preview
+
+```
+
+[component-metadata:sl-icon-button]
diff --git a/src/components.d.ts b/src/components.d.ts
index e06086b49..d86a965aa 100644
--- a/src/components.d.ts
+++ b/src/components.d.ts
@@ -319,6 +319,24 @@ export namespace Components {
*/
"src": string;
}
+ interface SlIconButton {
+ /**
+ * Set to true to disable the button.
+ */
+ "disabled": boolean;
+ /**
+ * An alternative description to use for accessibility. If omitted, the name or src will be used to generate it.
+ */
+ "label": string;
+ /**
+ * The name of the icon to draw. See the icon component for a full list of icons.
+ */
+ "name": string;
+ /**
+ * An external URL of an SVG file.
+ */
+ "src": string;
+ }
interface SlInput {
/**
* The input's autocaptialize attribute.
@@ -956,6 +974,12 @@ declare global {
prototype: HTMLSlIconElement;
new (): HTMLSlIconElement;
};
+ interface HTMLSlIconButtonElement extends Components.SlIconButton, HTMLStencilElement {
+ }
+ var HTMLSlIconButtonElement: {
+ prototype: HTMLSlIconButtonElement;
+ new (): HTMLSlIconButtonElement;
+ };
interface HTMLSlInputElement extends Components.SlInput, HTMLStencilElement {
}
var HTMLSlInputElement: {
@@ -1084,6 +1108,7 @@ declare global {
"sl-dropdown": HTMLSlDropdownElement;
"sl-form": HTMLSlFormElement;
"sl-icon": HTMLSlIconElement;
+ "sl-icon-button": HTMLSlIconButtonElement;
"sl-input": HTMLSlInputElement;
"sl-menu": HTMLSlMenuElement;
"sl-menu-divider": HTMLSlMenuDividerElement;
@@ -1491,6 +1516,24 @@ declare namespace LocalJSX {
*/
"src"?: string;
}
+ interface SlIconButton {
+ /**
+ * Set to true to disable the button.
+ */
+ "disabled"?: boolean;
+ /**
+ * An alternative description to use for accessibility. If omitted, the name or src will be used to generate it.
+ */
+ "label"?: string;
+ /**
+ * The name of the icon to draw. See the icon component for a full list of icons.
+ */
+ "name"?: string;
+ /**
+ * An external URL of an SVG file.
+ */
+ "src"?: string;
+ }
interface SlInput {
/**
* The input's autocaptialize attribute.
@@ -2082,6 +2125,7 @@ declare namespace LocalJSX {
"sl-dropdown": SlDropdown;
"sl-form": SlForm;
"sl-icon": SlIcon;
+ "sl-icon-button": SlIconButton;
"sl-input": SlInput;
"sl-menu": SlMenu;
"sl-menu-divider": SlMenuDivider;
@@ -2120,6 +2164,7 @@ declare module "@stencil/core" {
"sl-dropdown": LocalJSX.SlDropdown & JSXBase.HTMLAttributes;
"sl-form": LocalJSX.SlForm & JSXBase.HTMLAttributes;
"sl-icon": LocalJSX.SlIcon & JSXBase.HTMLAttributes;
+ "sl-icon-button": LocalJSX.SlIconButton & JSXBase.HTMLAttributes;
"sl-input": LocalJSX.SlInput & JSXBase.HTMLAttributes;
"sl-menu": LocalJSX.SlMenu & JSXBase.HTMLAttributes;
"sl-menu-divider": LocalJSX.SlMenuDivider & JSXBase.HTMLAttributes;
diff --git a/src/components/alert/alert.scss b/src/components/alert/alert.scss
index e7f4f8df3..4911db012 100644
--- a/src/components/alert/alert.scss
+++ b/src/components/alert/alert.scss
@@ -90,30 +90,6 @@
flex: 0 0 auto;
display: flex;
align-items: center;
- background: none;
- border: none;
- border-radius: var(--sl-border-radius-small);
- font-family: inherit;
font-size: var(--sl-font-size-large);
- font-weight: inherit;
- color: var(--sl-color-gray-50);
- padding: 0 var(--sl-spacing-large);
- cursor: pointer;
- transition: var(--sl-transition-fast) color;
- -webkit-appearance: none;
-
- &:hover,
- &:focus,
- &:active {
- color: var(--sl-color-primary-50);
- }
-
- &:focus {
- outline: none;
- }
-}
-
-.focus-visible .alert__close:focus {
- box-shadow: 0 0 0 var(--sl-focus-ring-width)
- hsla(var(--sl-color-primary-hue), var(--sl-color-primary-saturation), 50%, var(--sl-focus-ring-alpha));
+ padding: 0 var(--sl-spacing-medium);
}
diff --git a/src/components/alert/alert.tsx b/src/components/alert/alert.tsx
index 90acfa4b5..ac77b6715 100644
--- a/src/components/alert/alert.tsx
+++ b/src/components/alert/alert.tsx
@@ -1,5 +1,4 @@
import { Component, Element, Event, EventEmitter, Host, Method, Prop, Watch, h } from '@stencil/core';
-import { focusVisible } from '../../utilities/focus-visible';
/**
* @since 2.0
@@ -7,7 +6,6 @@ import { focusVisible } from '../../utilities/focus-visible';
*
* @slot - The alert's content.
* @slot icon - An icon to show in the alert.
- * @slot close-icon - An icon to use in lieu of the default close icon.
*
* @part base - The component's base wrapper.
* @part icon - The container that wraps the alert icon.
@@ -57,18 +55,12 @@ export class Tab {
}
componentDidLoad() {
- focusVisible.observe(this.alert);
-
// Show on init if open
if (this.open) {
this.show();
}
}
- componentDidUnload() {
- focusVisible.unobserve(this.alert);
- }
-
/** Shows the alert. */
@Method()
async show() {
@@ -140,11 +132,7 @@ export class Tab {
{this.closable && (
-
+
)}
diff --git a/src/components/dialog/dialog.scss b/src/components/dialog/dialog.scss
index dd59083d5..14d521984 100644
--- a/src/components/dialog/dialog.scss
+++ b/src/components/dialog/dialog.scss
@@ -72,31 +72,8 @@
flex: 0 0 auto;
display: flex;
align-items: center;
- background: none;
- border: none;
- border-radius: var(--sl-border-radius-small);
- font-family: inherit;
font-size: var(--sl-font-size-x-large);
- font-weight: inherit;
- color: var(--sl-color-gray-50);
padding: 0 var(--sl-spacing-large);
- cursor: pointer;
- transition: var(--sl-transition-fast) color;
- -webkit-appearance: none;
-
- &:hover,
- &:focus,
- &:active {
- color: var(--sl-color-primary-50);
- }
-
- &:focus {
- outline: none;
- }
-}
-
-.focus-visible .dialog__close:focus {
- box-shadow: var(--sl-focus-ring-box-shadow);
}
.dialog__body {
diff --git a/src/components/dialog/dialog.tsx b/src/components/dialog/dialog.tsx
index 953e1eb09..fbf36eb1c 100644
--- a/src/components/dialog/dialog.tsx
+++ b/src/components/dialog/dialog.tsx
@@ -1,6 +1,5 @@
import { Component, Element, Event, EventEmitter, Method, Prop, State, Watch, h } from '@stencil/core';
import { lockBodyScrolling, unlockBodyScrolling } from '../../utilities/scroll';
-import { focusVisible } from '../../utilities/focus-visible';
import { hasSlot } from '../../utilities/slot';
let id = 0;
@@ -85,8 +84,6 @@ export class Dialog {
}
componentDidLoad() {
- focusVisible.observe(this.dialog);
-
// Show on init if open
if (this.open) {
this.show();
@@ -94,7 +91,6 @@ export class Dialog {
}
componentDidUnload() {
- focusVisible.unobserve(this.dialog);
unlockBodyScrolling(this.host);
this.host.shadowRoot.removeEventListener('slotchange', this.updateSlots);
@@ -209,9 +205,7 @@ export class Dialog {
{/* If there's no label, use an invisible character to prevent the heading from collapsing */}
{this.label || String.fromCharCode(65279)}
-
+
)}
diff --git a/src/components/drawer/drawer.scss b/src/components/drawer/drawer.scss
index a5be45547..612cf77e1 100644
--- a/src/components/drawer/drawer.scss
+++ b/src/components/drawer/drawer.scss
@@ -110,31 +110,8 @@
flex: 0 0 auto;
display: flex;
align-items: center;
- background: none;
- border: none;
- border-radius: var(--sl-border-radius-small);
- font-family: inherit;
font-size: var(--sl-font-size-x-large);
- font-weight: inherit;
- color: var(--sl-color-gray-50);
padding: 0 var(--sl-spacing-large);
- cursor: pointer;
- transition: var(--sl-transition-fast) color;
- -webkit-appearance: none;
-
- &:hover,
- &:focus,
- &:active {
- color: var(--sl-color-primary-50);
- }
-
- &:focus {
- outline: none;
- }
-}
-
-.focus-visible .drawer__close:focus {
- box-shadow: var(--sl-focus-ring-box-shadow);
}
.drawer__body {
diff --git a/src/components/drawer/drawer.tsx b/src/components/drawer/drawer.tsx
index 502859ff5..3065f874b 100644
--- a/src/components/drawer/drawer.tsx
+++ b/src/components/drawer/drawer.tsx
@@ -1,6 +1,5 @@
import { Component, Element, Event, EventEmitter, Method, Prop, State, Watch, h } from '@stencil/core';
import { lockBodyScrolling, unlockBodyScrolling } from '../../utilities/scroll';
-import { focusVisible } from '../../utilities/focus-visible';
import { hasSlot } from '../../utilities/slot';
let id = 0;
@@ -93,8 +92,6 @@ export class Drawer {
}
componentDidLoad() {
- focusVisible.observe(this.drawer);
-
// Show on init if open
if (this.open) {
this.show();
@@ -102,7 +99,6 @@ export class Drawer {
}
componentDidUnload() {
- focusVisible.unobserve(this.drawer);
unlockBodyScrolling(this.host);
this.host.shadowRoot.removeEventListener('slotchange', this.updateSlots);
@@ -229,9 +225,7 @@ export class Drawer {
{/* If there's no label, use an invisible character to prevent the heading from collapsing */}
{this.label || String.fromCharCode(65279)}
-
+
)}
diff --git a/src/components/icon-button/icon-button.scss b/src/components/icon-button/icon-button.scss
new file mode 100644
index 000000000..89a1d88aa
--- /dev/null
+++ b/src/components/icon-button/icon-button.scss
@@ -0,0 +1,42 @@
+@import 'component';
+
+:host {
+ display: inline-block;
+}
+
+.icon-button {
+ flex: 0 0 auto;
+ display: flex;
+ align-items: center;
+ background: none;
+ border: none;
+ border-radius: var(--sl-border-radius-medium);
+ font-size: inherit;
+ color: var(--sl-color-gray-50);
+ padding: var(--sl-spacing-x-small);
+ cursor: pointer;
+ transition: var(--sl-transition-medium) color;
+ -webkit-appearance: none;
+
+ &:hover:not(.icon-button--disabled),
+ &:focus:not(.icon-button--disabled) {
+ color: var(--sl-color-primary-50);
+ }
+
+ &:active:not(.icon-button--disabled) {
+ color: var(--sl-color-primary-40);
+ }
+
+ &:focus {
+ outline: none;
+ }
+}
+
+.icon-button--disabled {
+ opacity: 0.5;
+ cursor: not-allowed;
+}
+
+.focus-visible.icon-button:focus {
+ box-shadow: var(--sl-focus-ring-box-shadow);
+}
diff --git a/src/components/icon-button/icon-button.tsx b/src/components/icon-button/icon-button.tsx
new file mode 100644
index 000000000..c786ec2b7
--- /dev/null
+++ b/src/components/icon-button/icon-button.tsx
@@ -0,0 +1,54 @@
+import { Component, Prop, h } from '@stencil/core';
+import { focusVisible } from '../../utilities/focus-visible';
+
+/**
+ * @since 2.0
+ * @status stable
+ *
+ * @part base - The component's base wrapper.
+ */
+
+@Component({
+ tag: 'sl-icon-button',
+ styleUrl: 'icon-button.scss',
+ shadow: true
+})
+export class IconButton {
+ button: HTMLButtonElement;
+
+ /** The name of the icon to draw. See the icon component for a full list of icons. */
+ @Prop() name: string;
+
+ /** An external URL of an SVG file. */
+ @Prop() src: string;
+
+ /** An alternative description to use for accessibility. If omitted, the name or src will be used to generate it. */
+ @Prop() label: string;
+
+ /** Set to true to disable the button. */
+ @Prop() disabled = false;
+
+ componentDidLoad() {
+ focusVisible.observe(this.button);
+ }
+
+ componentDidUnload() {
+ focusVisible.unobserve(this.button);
+ }
+
+ render() {
+ return (
+
+ );
+ }
+}
diff --git a/src/components/tag/tag.scss b/src/components/tag/tag.scss
index 59375f4ec..5704317d1 100644
--- a/src/components/tag/tag.scss
+++ b/src/components/tag/tag.scss
@@ -14,15 +14,9 @@
cursor: default;
}
-.tag__clear {
- display: flex;
- align-items: center;
- border-radius: var(--sl-border-radius-small);
- cursor: pointer;
-
- &:focus {
- outline: none;
- }
+.tag__clear::part(base) {
+ color: inherit;
+ padding: 0;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
diff --git a/src/components/tag/tag.tsx b/src/components/tag/tag.tsx
index bd69e15a6..94bac5ead 100644
--- a/src/components/tag/tag.tsx
+++ b/src/components/tag/tag.tsx
@@ -73,9 +73,7 @@ export class Tag {
{this.clearable && (
-
-
-
+
)}
);