'How to make a rounded button tkinter?
I am trying to get rounded buttons for my script using tkinter.
I found the following code in an answer to How to make a Button using the tkinter Canvas widget?:
from tkinter import *
import tkinter as tk
class CustomButton(tk.Canvas):
def __init__(self, parent, width, height, color, command=None):
tk.Canvas.__init__(self, parent, borderwidth=1,
relief="raised", highlightthickness=0)
self.command = command
padding = 4
id = self.create_oval((padding,padding,
width+padding, height+padding), outline=color, fill=color)
(x0,y0,x1,y1) = self.bbox("all")
width = (x1-x0) + padding
height = (y1-y0) + padding
self.configure(width=width, height=height)
self.bind("<ButtonPress-1>", self._on_press)
self.bind("<ButtonRelease-1>", self._on_release)
def _on_press(self, event):
self.configure(relief="sunken")
def _on_release(self, event):
self.configure(relief="raised")
if self.command is not None:
self.command()
app = CustomButton()
app.mainloop()
but I get the following error:
TypeError: __init__() missing 4 required positional arguments: 'parent', 'width', 'height', and 'color'
Solution 1:[1]
A very easy way to make a rounded button in tkinter is to use an image.
First create an image of what you want you button to look like save it as a .png and remove the outside background so it is rounded like the one below:

Next insert the image in a button with PhotoImage like this:
self.loadimage = tk.PhotoImage(file="rounded_button.png")
self.roundedbutton = tk.Button(self, image=self.loadimage)
self.roundedbutton["bg"] = "white"
self.roundedbutton["border"] = "0"
self.roundedbutton.pack(side="top")
Ensure to use border="0" and the button border will be removed.
I added the self.roundedborder["bg"] = "white" so that the the background the background of the button is the same as the Tkinter window.
The great part is that you can use any shape you like not just the normal button shapes.
Solution 2:[2]
I made this rounded rectangle button if anyone was looking for more of an apple look or something. For convenience here are the arguments:
RoundedButton(parent, width, height, cornerradius, padding, fillcolor, background, command)
Note: If the corner radius is greater than half of the width or height an error message will be sent in the terminal. Pill shapes can still be made through if you set the corner radius to exactly half of the height or width.
Finally the code:
from tkinter import *
import tkinter as tk
root = Tk()
class RoundedButton(tk.Canvas):
def __init__(self, parent, width, height, cornerradius, padding, color, bg, command=None):
tk.Canvas.__init__(self, parent, borderwidth=0,
relief="flat", highlightthickness=0, bg=bg)
self.command = command
if cornerradius > 0.5*width:
print("Error: cornerradius is greater than width.")
return None
if cornerradius > 0.5*height:
print("Error: cornerradius is greater than height.")
return None
rad = 2*cornerradius
def shape():
self.create_polygon((padding,height-cornerradius-padding,padding,cornerradius+padding,padding+cornerradius,padding,width-padding-cornerradius,padding,width-padding,cornerradius+padding,width-padding,height-cornerradius-padding,width-padding-cornerradius,height-padding,padding+cornerradius,height-padding), fill=color, outline=color)
self.create_arc((padding,padding+rad,padding+rad,padding), start=90, extent=90, fill=color, outline=color)
self.create_arc((width-padding-rad,padding,width-padding,padding+rad), start=0, extent=90, fill=color, outline=color)
self.create_arc((width-padding,height-rad-padding,width-padding-rad,height-padding), start=270, extent=90, fill=color, outline=color)
self.create_arc((padding,height-padding-rad,padding+rad,height-padding), start=180, extent=90, fill=color, outline=color)
id = shape()
(x0,y0,x1,y1) = self.bbox("all")
width = (x1-x0)
height = (y1-y0)
self.configure(width=width, height=height)
self.bind("<ButtonPress-1>", self._on_press)
self.bind("<ButtonRelease-1>", self._on_release)
def _on_press(self, event):
self.configure(relief="sunken")
def _on_release(self, event):
self.configure(relief="raised")
if self.command is not None:
self.command()
def test():
print("Hello")
canvas = Canvas(root, height=300, width=500)
canvas.pack()
button = RoundedButton(root, 200, 100, 50, 2, 'red', 'white', command=test)
button.place(relx=.1, rely=.1)
root.mainloop()
Solution 3:[3]
You need to create root window first (or some other widget) and give it to your CustomButton together with different parameters (see definition of __init__ method).
Try instead of app = CustomButton() the following:
app = tk.Tk()
button = CustomButton(app, 100, 25, 'red')
button.pack()
app.mainloop()
Solution 4:[4]
You are not passing any arguments to the constructor.
Specifically, on this line
app = CustomButton()
you need to pass the arguments that were defined in the constructor definition, namely parent, width, height and color.
Solution 5:[5]
Unfortunately, images don't work well when resized.
Below is an example of a rounded button using canvas which works well even if resized.
import tkinter as tk
class RoundedButton(tk.Canvas):
def __init__(self, master=None, text:str="", radius=25, btnforeground="#000000", btnbackground="#ffffff", clicked=None, *args, **kwargs):
super(RoundedButton, self).__init__(master, *args, **kwargs)
self.config(bg=self.master["bg"])
self.btnbackground = btnbackground
self.clicked = clicked
self.radius = radius
self.rect = self.round_rectangle(0, 0, 0, 0, tags="button", radius=radius, fill=btnbackground)
self.text = self.create_text(0, 0, text=text, tags="button", fill=btnforeground, font=("Times", 30), justify="center")
self.tag_bind("button", "<ButtonPress>", self.border)
self.tag_bind("button", "<ButtonRelease>", self.border)
self.bind("<Configure>", self.resize)
text_rect = self.bbox(self.text)
if int(self["width"]) < text_rect[2]-text_rect[0]:
self["width"] = (text_rect[2]-text_rect[0]) + 10
if int(self["height"]) < text_rect[3]-text_rect[1]:
self["height"] = (text_rect[3]-text_rect[1]) + 10
def round_rectangle(self, x1, y1, x2, y2, radius=25, update=False, **kwargs): # if update is False a new rounded rectangle's id will be returned else updates existing rounded rect.
# source: https://stackoverflow.com/a/44100075/15993687
points = [x1+radius, y1,
x1+radius, y1,
x2-radius, y1,
x2-radius, y1,
x2, y1,
x2, y1+radius,
x2, y1+radius,
x2, y2-radius,
x2, y2-radius,
x2, y2,
x2-radius, y2,
x2-radius, y2,
x1+radius, y2,
x1+radius, y2,
x1, y2,
x1, y2-radius,
x1, y2-radius,
x1, y1+radius,
x1, y1+radius,
x1, y1]
if not update:
return self.create_polygon(points, **kwargs, smooth=True)
else:
self.coords(self.rect, points)
def resize(self, event):
text_bbox = self.bbox(self.text)
if self.radius > event.width or self.radius > event.height:
radius = min((event.width, event.height))
else:
radius = self.radius
width, height = event.width, event.height
if event.width < text_bbox[2]-text_bbox[0]:
width = text_bbox[2]-text_bbox[0] + 30
if event.height < text_bbox[3]-text_bbox[1]:
height = text_bbox[3]-text_bbox[1] + 30
self.round_rectangle(5, 5, width-5, height-5, radius, update=True)
bbox = self.bbox(self.rect)
x = ((bbox[2]-bbox[0])/2) - ((text_bbox[2]-text_bbox[0])/2)
y = ((bbox[3]-bbox[1])/2) - ((text_bbox[3]-text_bbox[1])/2)
self.moveto(self.text, x, y)
def border(self, event):
if event.type == "4":
self.itemconfig(self.rect, fill="#d2d6d3")
if self.clicked is not None:
self.clicked()
else:
self.itemconfig(self.rect, fill=self.btnbackground)
def func():
print("Button pressed")
root = tk.Tk()
btn = RoundedButton(text="This is a \n rounded button", radius=100, btnbackground="#0078ff", btnforeground="#ffffff", clicked=func)
btn.pack(expand=True, fill="both")
root.mainloop()
To create this use canvas.create_rectangle() and canvas.create_text() methods and give them both of them same tag, say "button". The tag will be used when using canvas.tag_bind("tag", "<ButtonPress>")(you can also simply pass "current" as tag, which is assigned to the currently selected item by tkinter, in which case you can remove button tag).
Use canvas.tag_bind on canvas item instead of bind on canvas, this way the button color will change only if the mouse press happens inside the rounded button and not at edges.
You can scale and improve this to generate custom events when clicked inside the button, add configure method to configure button text and background, etc.
Solution 6:[6]
If you use an image such as in @Xantium 's method, you could set the button parameter borderwidth to 0.
As in:
homebtn = tk.Button(root, image=img, borderwidth=0)
Solution 7:[7]
I have had a lot of trouble finding the code that works for me. I have tried applying images to buttons and also tried the custom button styles from above.
This is the custom button code that worked for me and I am thankful for this issue on Github
Here is the code just in case :
from tkinter import *
import tkinter as tk
import tkinter.font as font
class RoundedButton(tk.Canvas):
def __init__(self, parent, border_radius, padding, color, text='', command=None):
tk.Canvas.__init__(self, parent, borderwidth=0,
relief="raised", highlightthickness=0, bg=parent["bg"])
self.command = command
font_size = 10
self.font = font.Font(size=font_size, family='Helvetica')
self.id = None
height = font_size + (1 * padding)
width = self.font.measure(text)+(1*padding)
width = width if width >= 80 else 80
if border_radius > 0.5*width:
print("Error: border_radius is greater than width.")
return None
if border_radius > 0.5*height:
print("Error: border_radius is greater than height.")
return None
rad = 2*border_radius
def shape():
self.create_arc((0, rad, rad, 0),
start=90, extent=90, fill=color, outline=color)
self.create_arc((width-rad, 0, width,
rad), start=0, extent=90, fill=color, outline=color)
self.create_arc((width, height-rad, width-rad,
height), start=270, extent=90, fill=color, outline=color)
self.create_arc((0, height-rad, rad, height), start=180, extent=90, fill=color, outline=color)
return self.create_polygon((0, height-border_radius, 0, border_radius, border_radius, 0, width-border_radius, 0, width,
border_radius, width, height-border_radius, width-border_radius, height, border_radius, height),
fill=color, outline=color)
id = shape()
(x0, y0, x1, y1) = self.bbox("all")
width = (x1-x0)
height = (y1-y0)
self.configure(width=width, height=height)
self.create_text(width/2, height/2,text=text, fill='black', font= self.font)
self.bind("<ButtonPress-1>", self._on_press)
self.bind("<ButtonRelease-1>", self._on_release)
def _on_press(self, event):
self.configure(relief="sunken")
def _on_release(self, event):
self.configure(relief="raised")
if self.command is not None:
self.command()
Now save this code in a file, for example, name it custombutton.py.
Next import this file in your current python file (like so: from custombutton import RoundedButton) and use it like so:
RoundedButton(root, text="Some Text", border_radius=2, padding=4, command=some_function, color="#cda989")
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | Zoe stands with Ukraine |
| Solution 2 | Guac |
| Solution 3 | avysk |
| Solution 4 | Community |
| Solution 5 | |
| Solution 6 | MrNiver632 |
| Solution 7 | Parth Rangarajan |

