diff --git a/superset-frontend/src/components/IconButton/IconButton.stories.tsx b/superset-frontend/src/components/IconButton/IconButton.stories.tsx
new file mode 100644
index 0000000000000..45435e70cc8ec
--- /dev/null
+++ b/superset-frontend/src/components/IconButton/IconButton.stories.tsx
@@ -0,0 +1,58 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+import React from 'react';
+import IconButton, { IconButtonProps } from '.';
+
+export default {
+ title: 'IconButton',
+ component: IconButton,
+};
+
+export const InteractiveIconButton = (args: IconButtonProps) => (
+
+);
+
+InteractiveIconButton.args = {
+ buttonText: 'This is the IconButton text',
+ altText: 'This is an example of non-default alt text',
+ href: 'https://preset.io/',
+ target: '_blank',
+};
+
+InteractiveIconButton.argTypes = {
+ icon: {
+ defaultValue: '/images/icons/sql.svg',
+ control: {
+ type: 'select',
+ options: [
+ '/images/icons/sql.svg',
+ '/images/icons/server.svg',
+ '/images/icons/image.svg',
+ 'Click to see example alt text',
+ ],
+ },
+ },
+};
diff --git a/superset-frontend/src/components/IconButton/IconButton.test.jsx b/superset-frontend/src/components/IconButton/IconButton.test.jsx
new file mode 100644
index 0000000000000..40490011953fa
--- /dev/null
+++ b/superset-frontend/src/components/IconButton/IconButton.test.jsx
@@ -0,0 +1,38 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+import React from 'react';
+import { render, screen } from 'spec/helpers/testing-library';
+import IconButton from 'src/components/IconButton';
+
+const defaultProps = {
+ buttonText: 'This is the IconButton text',
+ icon: '/images/icons/sql.svg',
+};
+
+describe('IconButton', () => {
+ it('renders an IconButton', () => {
+ render();
+
+ const icon = screen.getByRole('img');
+ const buttonText = screen.getByText(/this is the iconbutton text/i);
+
+ expect(icon).toBeVisible();
+ expect(buttonText).toBeVisible();
+ });
+});
diff --git a/superset-frontend/src/components/IconButton/index.tsx b/superset-frontend/src/components/IconButton/index.tsx
new file mode 100644
index 0000000000000..e7f9c2d89d528
--- /dev/null
+++ b/superset-frontend/src/components/IconButton/index.tsx
@@ -0,0 +1,123 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+import React from 'react';
+import { styled } from '@superset-ui/core';
+import Button from 'src/components/Button';
+import { ButtonProps as AntdButtonProps } from 'antd/lib/button';
+
+export interface IconButtonProps extends AntdButtonProps {
+ buttonText: string;
+ icon: string;
+ altText?: string;
+}
+
+const StyledButton = styled(Button)`
+ height: auto;
+ display: flex;
+ flex-direction: column;
+ padding: 0;
+`;
+const StyledImage = styled.div`
+ margin: ${({ theme }) => theme.gridUnit * 8}px 0;
+ padding: ${({ theme }) => theme.gridUnit * 4}px;
+
+ &:first-of-type {
+ margin-right: 0;
+ }
+
+ img {
+ width: fit-content;
+
+ &:first-of-type {
+ margin-right: 0;
+ }
+ }
+`;
+
+const StyledInner = styled.div`
+ max-height: calc(1.5em * 2);
+ overflow: hidden;
+ padding-right: 1rem;
+ position: relative;
+ white-space: break-spaces;
+
+ &::before {
+ content: '...';
+ inset-block-end: 0; /* "bottom" */
+ inset-inline-end: 8px; /* "right" */
+ position: absolute;
+ }
+
+ &::after {
+ background-color: ${({ theme }) => theme.colors.grayscale.light4};
+ content: '';
+ height: 1rem;
+ inset-inline-end: 8px; /* "right" */
+ position: absolute;
+ top: 4px;
+ width: 1rem;
+ }
+`;
+
+const StyledBottom = styled.div`
+ padding: ${({ theme }) => theme.gridUnit * 6}px
+ ${({ theme }) => theme.gridUnit * 4}px;
+ border-radius: 0 0 ${({ theme }) => theme.borderRadius}px
+ ${({ theme }) => theme.borderRadius}px;
+ background-color: ${({ theme }) => theme.colors.grayscale.light4};
+ width: 100%;
+ line-height: 1.5em;
+ overflow: hidden;
+ white-space: no-wrap;
+ text-overflow: ellipsis;
+
+ &:first-of-type {
+ margin-right: 0;
+ }
+`;
+
+const IconButton = styled(
+ ({ icon, altText, buttonText, ...props }: IconButtonProps) => (
+
+
+
+
+
+ {buttonText}
+
+
+ ),
+)`
+ text-transform: none;
+ background-color: ${({ theme }) => theme.colors.grayscale.light5};
+ font-weight: ${({ theme }) => theme.typography.weights.normal};
+ color: ${({ theme }) => theme.colors.grayscale.dark2};
+ border: 1px solid ${({ theme }) => theme.colors.grayscale.light2};
+ margin: 0;
+ width: 100%;
+
+ &:hover,
+ &:focus {
+ background-color: ${({ theme }) => theme.colors.grayscale.light5};
+ color: ${({ theme }) => theme.colors.grayscale.dark2};
+ border: 1px solid ${({ theme }) => theme.colors.grayscale.light2};
+ }
+`;
+
+export default IconButton;